@frontmcp/uipack 0.12.2 → 1.0.0-beta.10

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (298) hide show
  1. package/CLAUDE.md +56 -154
  2. package/README.md +367 -62
  3. package/adapters/base-template.d.ts +30 -0
  4. package/adapters/base-template.d.ts.map +1 -0
  5. package/adapters/cdn-info.d.ts +34 -0
  6. package/adapters/cdn-info.d.ts.map +1 -0
  7. package/adapters/constants.d.ts +18 -0
  8. package/adapters/constants.d.ts.map +1 -0
  9. package/adapters/content-detector.d.ts +19 -0
  10. package/adapters/content-detector.d.ts.map +1 -0
  11. package/adapters/content-renderers.d.ts +27 -0
  12. package/adapters/content-renderers.d.ts.map +1 -0
  13. package/adapters/index.d.ts +14 -7
  14. package/adapters/index.d.ts.map +1 -1
  15. package/adapters/index.js +2343 -426
  16. package/adapters/render-failure.d.ts +18 -0
  17. package/adapters/render-failure.d.ts.map +1 -0
  18. package/adapters/response-builder.d.ts +34 -104
  19. package/adapters/response-builder.d.ts.map +1 -1
  20. package/adapters/serving-mode.d.ts +28 -91
  21. package/adapters/serving-mode.d.ts.map +1 -1
  22. package/adapters/template-renderer.d.ts +50 -0
  23. package/adapters/template-renderer.d.ts.map +1 -0
  24. package/adapters/type-detector.d.ts +18 -0
  25. package/adapters/type-detector.d.ts.map +1 -0
  26. package/bridge-runtime/index.js +1 -1
  27. package/component/index.d.ts +14 -0
  28. package/component/index.d.ts.map +1 -0
  29. package/component/index.js +2043 -0
  30. package/component/loader.d.ts +36 -0
  31. package/component/loader.d.ts.map +1 -0
  32. package/component/renderer.d.ts +30 -0
  33. package/component/renderer.d.ts.map +1 -0
  34. package/component/transpiler.d.ts +49 -0
  35. package/component/transpiler.d.ts.map +1 -0
  36. package/component/types.d.ts +82 -0
  37. package/component/types.d.ts.map +1 -0
  38. package/esm/adapters/index.mjs +2337 -422
  39. package/esm/bridge-runtime/index.mjs +1 -1
  40. package/esm/component/index.mjs +2013 -0
  41. package/esm/index.mjs +3446 -13935
  42. package/esm/package.json +4 -13
  43. package/esm/resolver/index.mjs +661 -0
  44. package/esm/shell/index.mjs +1406 -0
  45. package/esm/types/index.mjs +11 -11
  46. package/esm/utils/index.mjs +53 -8
  47. package/index.d.ts +12 -40
  48. package/index.d.ts.map +1 -1
  49. package/index.js +3579 -14218
  50. package/package.json +4 -13
  51. package/resolver/cdn-registry.d.ts +39 -0
  52. package/resolver/cdn-registry.d.ts.map +1 -0
  53. package/resolver/esm-sh.resolver.d.ts +54 -0
  54. package/resolver/esm-sh.resolver.d.ts.map +1 -0
  55. package/resolver/import-map.d.ts +47 -0
  56. package/resolver/import-map.d.ts.map +1 -0
  57. package/resolver/import-parser.d.ts +28 -0
  58. package/resolver/import-parser.d.ts.map +1 -0
  59. package/resolver/import-rewriter.d.ts +29 -0
  60. package/resolver/import-rewriter.d.ts.map +1 -0
  61. package/resolver/index.d.ts +15 -0
  62. package/resolver/index.d.ts.map +1 -0
  63. package/resolver/index.js +708 -0
  64. package/resolver/types.d.ts +191 -0
  65. package/resolver/types.d.ts.map +1 -0
  66. package/shell/builder.d.ts +31 -0
  67. package/shell/builder.d.ts.map +1 -0
  68. package/shell/csp.d.ts +37 -0
  69. package/shell/csp.d.ts.map +1 -0
  70. package/shell/custom-shell-applier.d.ts +33 -0
  71. package/shell/custom-shell-applier.d.ts.map +1 -0
  72. package/shell/custom-shell-resolver.d.ts +47 -0
  73. package/shell/custom-shell-resolver.d.ts.map +1 -0
  74. package/shell/custom-shell-types.d.ts +75 -0
  75. package/shell/custom-shell-types.d.ts.map +1 -0
  76. package/shell/custom-shell-validator.d.ts +26 -0
  77. package/shell/custom-shell-validator.d.ts.map +1 -0
  78. package/shell/data-injector.d.ts +40 -0
  79. package/shell/data-injector.d.ts.map +1 -0
  80. package/shell/index.d.ts +19 -0
  81. package/shell/index.d.ts.map +1 -0
  82. package/shell/index.js +1453 -0
  83. package/shell/types.d.ts +54 -0
  84. package/shell/types.d.ts.map +1 -0
  85. package/types/index.d.ts +1 -3
  86. package/types/index.d.ts.map +1 -1
  87. package/types/index.js +11 -11
  88. package/types/ui-config.d.ts +50 -11
  89. package/types/ui-config.d.ts.map +1 -1
  90. package/types/ui-runtime.d.ts +8 -82
  91. package/types/ui-runtime.d.ts.map +1 -1
  92. package/utils/index.d.ts +9 -3
  93. package/utils/index.d.ts.map +1 -1
  94. package/utils/index.js +59 -7
  95. package/adapters/platform-meta.constants.d.ts +0 -26
  96. package/adapters/platform-meta.constants.d.ts.map +0 -1
  97. package/adapters/platform-meta.d.ts +0 -234
  98. package/adapters/platform-meta.d.ts.map +0 -1
  99. package/base-template/bridge.d.ts +0 -90
  100. package/base-template/bridge.d.ts.map +0 -1
  101. package/base-template/default-base-template.d.ts +0 -91
  102. package/base-template/default-base-template.d.ts.map +0 -1
  103. package/base-template/index.d.ts +0 -15
  104. package/base-template/index.d.ts.map +0 -1
  105. package/base-template/index.js +0 -1393
  106. package/base-template/polyfills.d.ts +0 -31
  107. package/base-template/polyfills.d.ts.map +0 -1
  108. package/base-template/theme-styles.d.ts +0 -74
  109. package/base-template/theme-styles.d.ts.map +0 -1
  110. package/build/builders/base-builder.d.ts +0 -124
  111. package/build/builders/base-builder.d.ts.map +0 -1
  112. package/build/builders/esbuild-config.d.ts +0 -94
  113. package/build/builders/esbuild-config.d.ts.map +0 -1
  114. package/build/builders/hybrid-builder.d.ts +0 -93
  115. package/build/builders/hybrid-builder.d.ts.map +0 -1
  116. package/build/builders/index.d.ts +0 -17
  117. package/build/builders/index.d.ts.map +0 -1
  118. package/build/builders/inline-builder.d.ts +0 -83
  119. package/build/builders/inline-builder.d.ts.map +0 -1
  120. package/build/builders/static-builder.d.ts +0 -78
  121. package/build/builders/static-builder.d.ts.map +0 -1
  122. package/build/builders/types.d.ts +0 -341
  123. package/build/builders/types.d.ts.map +0 -1
  124. package/build/cdn-resources.d.ts +0 -244
  125. package/build/cdn-resources.d.ts.map +0 -1
  126. package/build/hybrid-data.d.ts +0 -127
  127. package/build/hybrid-data.d.ts.map +0 -1
  128. package/build/index.d.ts +0 -299
  129. package/build/index.d.ts.map +0 -1
  130. package/build/index.js +0 -8699
  131. package/build/ui-components-browser.d.ts +0 -64
  132. package/build/ui-components-browser.d.ts.map +0 -1
  133. package/build/widget-manifest.d.ts +0 -362
  134. package/build/widget-manifest.d.ts.map +0 -1
  135. package/bundler/cache.d.ts +0 -173
  136. package/bundler/cache.d.ts.map +0 -1
  137. package/bundler/file-cache/component-builder.d.ts +0 -167
  138. package/bundler/file-cache/component-builder.d.ts.map +0 -1
  139. package/bundler/file-cache/hash-calculator.d.ts +0 -155
  140. package/bundler/file-cache/hash-calculator.d.ts.map +0 -1
  141. package/bundler/file-cache/index.d.ts +0 -12
  142. package/bundler/file-cache/index.d.ts.map +0 -1
  143. package/bundler/file-cache/storage/filesystem.d.ts +0 -149
  144. package/bundler/file-cache/storage/filesystem.d.ts.map +0 -1
  145. package/bundler/file-cache/storage/index.d.ts +0 -11
  146. package/bundler/file-cache/storage/index.d.ts.map +0 -1
  147. package/bundler/file-cache/storage/interface.d.ts +0 -152
  148. package/bundler/file-cache/storage/interface.d.ts.map +0 -1
  149. package/bundler/file-cache/storage/redis.d.ts +0 -139
  150. package/bundler/file-cache/storage/redis.d.ts.map +0 -1
  151. package/bundler/index.d.ts +0 -35
  152. package/bundler/index.d.ts.map +0 -1
  153. package/bundler/index.js +0 -2953
  154. package/bundler/sandbox/enclave-adapter.d.ts +0 -121
  155. package/bundler/sandbox/enclave-adapter.d.ts.map +0 -1
  156. package/bundler/sandbox/executor.d.ts +0 -14
  157. package/bundler/sandbox/executor.d.ts.map +0 -1
  158. package/bundler/sandbox/policy.d.ts +0 -62
  159. package/bundler/sandbox/policy.d.ts.map +0 -1
  160. package/bundler/types.d.ts +0 -702
  161. package/bundler/types.d.ts.map +0 -1
  162. package/dependency/cdn-registry.d.ts +0 -98
  163. package/dependency/cdn-registry.d.ts.map +0 -1
  164. package/dependency/import-map.d.ts +0 -186
  165. package/dependency/import-map.d.ts.map +0 -1
  166. package/dependency/import-parser.d.ts +0 -82
  167. package/dependency/import-parser.d.ts.map +0 -1
  168. package/dependency/index.d.ts +0 -17
  169. package/dependency/index.d.ts.map +0 -1
  170. package/dependency/index.js +0 -3180
  171. package/dependency/resolver.d.ts +0 -164
  172. package/dependency/resolver.d.ts.map +0 -1
  173. package/dependency/schemas.d.ts +0 -486
  174. package/dependency/schemas.d.ts.map +0 -1
  175. package/dependency/template-loader.d.ts +0 -204
  176. package/dependency/template-loader.d.ts.map +0 -1
  177. package/dependency/template-processor.d.ts +0 -118
  178. package/dependency/template-processor.d.ts.map +0 -1
  179. package/dependency/types.d.ts +0 -739
  180. package/dependency/types.d.ts.map +0 -1
  181. package/esm/base-template/index.mjs +0 -1359
  182. package/esm/build/index.mjs +0 -8601
  183. package/esm/bundler/index.mjs +0 -2895
  184. package/esm/dependency/index.mjs +0 -3068
  185. package/esm/handlebars/index.mjs +0 -587
  186. package/esm/registry/index.mjs +0 -6305
  187. package/esm/renderers/index.mjs +0 -1557
  188. package/esm/runtime/index.mjs +0 -5361
  189. package/esm/styles/index.mjs +0 -171
  190. package/esm/theme/index.mjs +0 -756
  191. package/esm/tool-template/index.mjs +0 -3652
  192. package/esm/validation/index.mjs +0 -542
  193. package/handlebars/expression-extractor.d.ts +0 -147
  194. package/handlebars/expression-extractor.d.ts.map +0 -1
  195. package/handlebars/helpers.d.ts +0 -339
  196. package/handlebars/helpers.d.ts.map +0 -1
  197. package/handlebars/index.d.ts +0 -195
  198. package/handlebars/index.d.ts.map +0 -1
  199. package/handlebars/index.js +0 -659
  200. package/preview/claude-preview.d.ts +0 -67
  201. package/preview/claude-preview.d.ts.map +0 -1
  202. package/preview/generic-preview.d.ts +0 -66
  203. package/preview/generic-preview.d.ts.map +0 -1
  204. package/preview/index.d.ts +0 -36
  205. package/preview/index.d.ts.map +0 -1
  206. package/preview/openai-preview.d.ts +0 -70
  207. package/preview/openai-preview.d.ts.map +0 -1
  208. package/preview/types.d.ts +0 -199
  209. package/preview/types.d.ts.map +0 -1
  210. package/registry/index.d.ts +0 -46
  211. package/registry/index.d.ts.map +0 -1
  212. package/registry/index.js +0 -6342
  213. package/registry/render-template.d.ts +0 -91
  214. package/registry/render-template.d.ts.map +0 -1
  215. package/registry/tool-ui.registry.d.ts +0 -294
  216. package/registry/tool-ui.registry.d.ts.map +0 -1
  217. package/registry/uri-utils.d.ts +0 -56
  218. package/registry/uri-utils.d.ts.map +0 -1
  219. package/renderers/cache.d.ts +0 -145
  220. package/renderers/cache.d.ts.map +0 -1
  221. package/renderers/html.renderer.d.ts +0 -123
  222. package/renderers/html.renderer.d.ts.map +0 -1
  223. package/renderers/index.d.ts +0 -36
  224. package/renderers/index.d.ts.map +0 -1
  225. package/renderers/index.js +0 -1603
  226. package/renderers/mdx-client.renderer.d.ts +0 -124
  227. package/renderers/mdx-client.renderer.d.ts.map +0 -1
  228. package/renderers/registry.d.ts +0 -133
  229. package/renderers/registry.d.ts.map +0 -1
  230. package/renderers/types.d.ts +0 -343
  231. package/renderers/types.d.ts.map +0 -1
  232. package/renderers/utils/detect.d.ts +0 -107
  233. package/renderers/utils/detect.d.ts.map +0 -1
  234. package/renderers/utils/hash.d.ts +0 -40
  235. package/renderers/utils/hash.d.ts.map +0 -1
  236. package/renderers/utils/index.d.ts +0 -9
  237. package/renderers/utils/index.d.ts.map +0 -1
  238. package/renderers/utils/transpiler.d.ts +0 -70
  239. package/renderers/utils/transpiler.d.ts.map +0 -1
  240. package/runtime/adapters/html.adapter.d.ts +0 -59
  241. package/runtime/adapters/html.adapter.d.ts.map +0 -1
  242. package/runtime/adapters/index.d.ts +0 -26
  243. package/runtime/adapters/index.d.ts.map +0 -1
  244. package/runtime/adapters/mdx.adapter.d.ts +0 -73
  245. package/runtime/adapters/mdx.adapter.d.ts.map +0 -1
  246. package/runtime/adapters/types.d.ts +0 -95
  247. package/runtime/adapters/types.d.ts.map +0 -1
  248. package/runtime/csp.d.ts +0 -48
  249. package/runtime/csp.d.ts.map +0 -1
  250. package/runtime/index.d.ts +0 -17
  251. package/runtime/index.d.ts.map +0 -1
  252. package/runtime/index.js +0 -5432
  253. package/runtime/mcp-bridge.d.ts +0 -101
  254. package/runtime/mcp-bridge.d.ts.map +0 -1
  255. package/runtime/renderer-runtime.d.ts +0 -133
  256. package/runtime/renderer-runtime.d.ts.map +0 -1
  257. package/runtime/sanitizer.d.ts +0 -180
  258. package/runtime/sanitizer.d.ts.map +0 -1
  259. package/runtime/types.d.ts +0 -415
  260. package/runtime/types.d.ts.map +0 -1
  261. package/runtime/wrapper.d.ts +0 -421
  262. package/runtime/wrapper.d.ts.map +0 -1
  263. package/styles/index.d.ts +0 -8
  264. package/styles/index.d.ts.map +0 -1
  265. package/styles/index.js +0 -222
  266. package/styles/variants.d.ts +0 -51
  267. package/styles/variants.d.ts.map +0 -1
  268. package/theme/cdn.d.ts +0 -195
  269. package/theme/cdn.d.ts.map +0 -1
  270. package/theme/css-to-theme.d.ts +0 -64
  271. package/theme/css-to-theme.d.ts.map +0 -1
  272. package/theme/index.d.ts +0 -19
  273. package/theme/index.d.ts.map +0 -1
  274. package/theme/index.js +0 -814
  275. package/theme/platforms.d.ts +0 -102
  276. package/theme/platforms.d.ts.map +0 -1
  277. package/theme/presets/github-openai.d.ts +0 -50
  278. package/theme/presets/github-openai.d.ts.map +0 -1
  279. package/theme/presets/index.d.ts +0 -11
  280. package/theme/presets/index.d.ts.map +0 -1
  281. package/theme/theme.d.ts +0 -396
  282. package/theme/theme.d.ts.map +0 -1
  283. package/tool-template/builder.d.ts +0 -213
  284. package/tool-template/builder.d.ts.map +0 -1
  285. package/tool-template/index.d.ts +0 -16
  286. package/tool-template/index.d.ts.map +0 -1
  287. package/tool-template/index.js +0 -3690
  288. package/validation/error-box.d.ts +0 -56
  289. package/validation/error-box.d.ts.map +0 -1
  290. package/validation/index.d.ts +0 -13
  291. package/validation/index.d.ts.map +0 -1
  292. package/validation/index.js +0 -576
  293. package/validation/schema-paths.d.ts +0 -118
  294. package/validation/schema-paths.d.ts.map +0 -1
  295. package/validation/template-validator.d.ts +0 -143
  296. package/validation/template-validator.d.ts.map +0 -1
  297. package/validation/wrapper.d.ts +0 -97
  298. package/validation/wrapper.d.ts.map +0 -1
@@ -0,0 +1,1406 @@
1
+ // libs/uipack/src/shell/csp.ts
2
+ var DEFAULT_CDN_DOMAINS = [
3
+ "https://cdn.jsdelivr.net",
4
+ "https://cdnjs.cloudflare.com",
5
+ "https://fonts.googleapis.com",
6
+ "https://fonts.gstatic.com",
7
+ "https://esm.sh"
8
+ ];
9
+ var DEFAULT_CSP_DIRECTIVES = [
10
+ "default-src 'none'",
11
+ `script-src 'self' 'unsafe-inline' ${DEFAULT_CDN_DOMAINS.join(" ")}`,
12
+ `style-src 'self' 'unsafe-inline' ${DEFAULT_CDN_DOMAINS.join(" ")}`,
13
+ `img-src 'self' data: ${DEFAULT_CDN_DOMAINS.join(" ")}`,
14
+ `font-src 'self' data: ${DEFAULT_CDN_DOMAINS.join(" ")}`,
15
+ `connect-src ${DEFAULT_CDN_DOMAINS.join(" ")}`,
16
+ "object-src 'self' data:"
17
+ ];
18
+ var RESTRICTIVE_CSP_DIRECTIVES = [
19
+ "default-src 'none'",
20
+ "script-src 'self' 'unsafe-inline'",
21
+ "style-src 'self' 'unsafe-inline'",
22
+ "img-src 'self' data:",
23
+ "font-src 'self' data:",
24
+ "connect-src 'none'",
25
+ "object-src 'self' data:"
26
+ ];
27
+ function buildCSPDirectives(csp) {
28
+ if (!csp) {
29
+ return [...DEFAULT_CSP_DIRECTIVES];
30
+ }
31
+ const validResourceDomains = sanitizeCSPDomains(csp.resourceDomains);
32
+ const validConnectDomains = sanitizeCSPDomains(csp.connectDomains);
33
+ const allResourceDomains = [.../* @__PURE__ */ new Set([...DEFAULT_CDN_DOMAINS, ...validResourceDomains])];
34
+ const directives = [
35
+ "default-src 'none'",
36
+ `script-src 'self' 'unsafe-inline' ${allResourceDomains.join(" ")}`,
37
+ `style-src 'self' 'unsafe-inline' ${allResourceDomains.join(" ")}`
38
+ ];
39
+ const imgSources = ["'self'", "data:", ...allResourceDomains];
40
+ directives.push(`img-src ${imgSources.join(" ")}`);
41
+ const fontSources = ["'self'", "data:", ...allResourceDomains];
42
+ directives.push(`font-src ${fontSources.join(" ")}`);
43
+ if (validConnectDomains.length) {
44
+ directives.push(`connect-src ${validConnectDomains.join(" ")}`);
45
+ } else {
46
+ directives.push(`connect-src ${allResourceDomains.join(" ")}`);
47
+ }
48
+ directives.push("object-src 'self' data:");
49
+ return directives;
50
+ }
51
+ function buildCSPMetaTag(csp) {
52
+ const directives = buildCSPDirectives(csp);
53
+ const content = directives.join("; ");
54
+ return `<meta http-equiv="Content-Security-Policy" content="${escapeAttribute(content)}">`;
55
+ }
56
+ function validateCSPDomain(domain) {
57
+ if (domain.startsWith("https://*.")) {
58
+ const rest = domain.slice(10);
59
+ return /^[a-zA-Z0-9]([a-zA-Z0-9-]*[a-zA-Z0-9])?\.[a-zA-Z]{2,}$/.test(rest);
60
+ }
61
+ try {
62
+ const url = new URL(domain);
63
+ return url.protocol === "https:";
64
+ } catch {
65
+ return false;
66
+ }
67
+ }
68
+ function sanitizeCSPDomains(domains) {
69
+ if (!domains) return [];
70
+ const valid = [];
71
+ for (const domain of domains) {
72
+ if (validateCSPDomain(domain)) {
73
+ valid.push(domain);
74
+ } else {
75
+ console.warn(`Invalid CSP domain ignored: ${domain}`);
76
+ }
77
+ }
78
+ return valid;
79
+ }
80
+ function escapeAttribute(str) {
81
+ return str.replace(/&/g, "&amp;").replace(/"/g, "&quot;").replace(/'/g, "&#39;").replace(/</g, "&lt;").replace(/>/g, "&gt;");
82
+ }
83
+
84
+ // libs/uipack/src/utils/index.ts
85
+ function escapeHtml(str) {
86
+ if (str === null || str === void 0) {
87
+ return "";
88
+ }
89
+ const s = String(str);
90
+ return s.replace(/&/g, "&amp;").replace(/</g, "&lt;").replace(/>/g, "&gt;").replace(/"/g, "&quot;").replace(/'/g, "&#39;").replace(/\u2028/g, "\\u2028").replace(/\u2029/g, "\\u2029");
91
+ }
92
+ function escapeScriptClose(jsonString) {
93
+ return jsonString.replace(/<\//g, "<\\/");
94
+ }
95
+ function safeJsonForScript(value) {
96
+ if (value === void 0) {
97
+ return "null";
98
+ }
99
+ try {
100
+ const jsonString = JSON.stringify(value, (_key, val) => {
101
+ if (typeof val === "bigint") {
102
+ return val.toString();
103
+ }
104
+ return val;
105
+ });
106
+ if (jsonString === void 0) {
107
+ return "null";
108
+ }
109
+ return escapeScriptClose(jsonString);
110
+ } catch {
111
+ return '{"error":"Value could not be serialized"}';
112
+ }
113
+ }
114
+
115
+ // libs/uipack/src/shell/data-injector.ts
116
+ function buildDataInjectionScript(options) {
117
+ const { toolName, input, output, structuredContent } = options;
118
+ const lines = [
119
+ `window.__mcpToolName = ${safeJsonForScript(toolName)};`,
120
+ `window.__mcpToolInput = ${safeJsonForScript(input ?? null)};`,
121
+ `window.__mcpToolOutput = ${safeJsonForScript(output ?? null)};`,
122
+ `window.__mcpStructuredContent = ${safeJsonForScript(structuredContent ?? null)};`
123
+ ];
124
+ return `<script>
125
+ ${lines.join("\n")}
126
+ </script>`;
127
+ }
128
+ var _uniqueIdCounter = 0;
129
+ function createTemplateHelpers() {
130
+ return {
131
+ escapeHtml: (str) => escapeHtml(str),
132
+ formatDate: (date, format) => {
133
+ const d = date instanceof Date ? date : new Date(date);
134
+ if (isNaN(d.getTime())) return String(date);
135
+ if (format === "iso") return d.toISOString();
136
+ if (format === "date") return d.toLocaleDateString();
137
+ if (format === "time") return d.toLocaleTimeString();
138
+ return d.toLocaleString();
139
+ },
140
+ formatCurrency: (amount, currency = "USD") => {
141
+ return new Intl.NumberFormat("en-US", {
142
+ style: "currency",
143
+ currency
144
+ }).format(amount);
145
+ },
146
+ uniqueId: (prefix = "mcp") => {
147
+ return `${prefix}-${++_uniqueIdCounter}`;
148
+ },
149
+ jsonEmbed: (data) => {
150
+ return escapeScriptClose(JSON.stringify(data));
151
+ }
152
+ };
153
+ }
154
+
155
+ // libs/uipack/src/bridge-runtime/iife-generator.ts
156
+ function generateBridgeIIFE(options = {}) {
157
+ const { debug = false, trustedOrigins = [], minify = false } = options;
158
+ const adapters = options.adapters || ["openai", "ext-apps", "claude", "gemini", "generic"];
159
+ const parts = [];
160
+ parts.push("(function() {");
161
+ parts.push('"use strict";');
162
+ parts.push("");
163
+ if (debug) {
164
+ parts.push('function log(msg) { console.log("[FrontMcpBridge] " + msg); }');
165
+ } else {
166
+ parts.push("function log() {}");
167
+ }
168
+ parts.push("");
169
+ parts.push("var DEFAULT_SAFE_AREA = { top: 0, bottom: 0, left: 0, right: 0 };");
170
+ parts.push("");
171
+ parts.push(generateContextDetection());
172
+ parts.push("");
173
+ parts.push(generateBaseCapabilities());
174
+ parts.push("");
175
+ if (adapters.includes("openai")) {
176
+ parts.push(generateOpenAIAdapter());
177
+ parts.push("");
178
+ }
179
+ if (adapters.includes("ext-apps")) {
180
+ parts.push(generateExtAppsAdapter(trustedOrigins));
181
+ parts.push("");
182
+ }
183
+ if (adapters.includes("claude")) {
184
+ parts.push(generateClaudeAdapter());
185
+ parts.push("");
186
+ }
187
+ if (adapters.includes("gemini")) {
188
+ parts.push(generateGeminiAdapter());
189
+ parts.push("");
190
+ }
191
+ if (adapters.includes("generic")) {
192
+ parts.push(generateGenericAdapter());
193
+ parts.push("");
194
+ }
195
+ parts.push(generatePlatformDetection(adapters));
196
+ parts.push("");
197
+ parts.push(generateBridgeClass());
198
+ parts.push("");
199
+ parts.push("var bridge = new FrontMcpBridge();");
200
+ parts.push("bridge.initialize().then(function() {");
201
+ parts.push(' log("Bridge initialized with adapter: " + bridge.adapterId);');
202
+ parts.push(' window.dispatchEvent(new CustomEvent("bridge:ready", { detail: { adapter: bridge.adapterId } }));');
203
+ parts.push("}).catch(function(err) {");
204
+ parts.push(' console.error("[FrontMcpBridge] Init failed:", err);');
205
+ parts.push(' window.dispatchEvent(new CustomEvent("bridge:error", { detail: { error: err } }));');
206
+ parts.push("});");
207
+ parts.push("");
208
+ parts.push("window.FrontMcpBridge = bridge;");
209
+ parts.push("})();");
210
+ const code = parts.join("\n");
211
+ if (minify) {
212
+ return minifyJS(code);
213
+ }
214
+ return code;
215
+ }
216
+ function generateContextDetection() {
217
+ return `
218
+ function detectTheme() {
219
+ if (typeof window !== 'undefined' && window.matchMedia) {
220
+ return window.matchMedia('(prefers-color-scheme: dark)').matches ? 'dark' : 'light';
221
+ }
222
+ return 'light';
223
+ }
224
+
225
+ function detectLocale() {
226
+ if (typeof navigator !== 'undefined') {
227
+ return navigator.language || 'en-US';
228
+ }
229
+ return 'en-US';
230
+ }
231
+
232
+ function detectUserAgent() {
233
+ if (typeof navigator === 'undefined') {
234
+ return { type: 'web', hover: true, touch: false };
235
+ }
236
+ var ua = navigator.userAgent || '';
237
+ var isMobile = /iPhone|iPad|iPod|Android/i.test(ua);
238
+ var hasTouch = 'ontouchstart' in window || navigator.maxTouchPoints > 0;
239
+ var hasHover = window.matchMedia && window.matchMedia('(hover: hover)').matches;
240
+ return { type: isMobile ? 'mobile' : 'web', hover: hasHover !== false, touch: hasTouch };
241
+ }
242
+
243
+ function detectViewport() {
244
+ if (typeof window !== 'undefined') {
245
+ return { width: window.innerWidth, height: window.innerHeight };
246
+ }
247
+ return undefined;
248
+ }
249
+
250
+ function readInjectedData() {
251
+ var data = { toolInput: {}, toolOutput: undefined, structuredContent: undefined };
252
+ if (typeof window !== 'undefined') {
253
+ if (window.__mcpToolInput) data.toolInput = window.__mcpToolInput;
254
+ if (window.__mcpToolOutput) data.toolOutput = window.__mcpToolOutput;
255
+ if (window.__mcpStructuredContent) data.structuredContent = window.__mcpStructuredContent;
256
+ }
257
+ return data;
258
+ }
259
+ `.trim();
260
+ }
261
+ function generateBaseCapabilities() {
262
+ return `
263
+ var DEFAULT_CAPABILITIES = {
264
+ canCallTools: false,
265
+ canSendMessages: false,
266
+ canOpenLinks: false,
267
+ canPersistState: true,
268
+ hasNetworkAccess: true,
269
+ supportsDisplayModes: false,
270
+ supportsTheme: true
271
+ };
272
+ `.trim();
273
+ }
274
+ function generateOpenAIAdapter() {
275
+ return `
276
+ var OpenAIAdapter = {
277
+ id: 'openai',
278
+ name: 'OpenAI ChatGPT',
279
+ priority: 100,
280
+ capabilities: Object.assign({}, DEFAULT_CAPABILITIES, {
281
+ canCallTools: true,
282
+ canSendMessages: true,
283
+ canOpenLinks: true,
284
+ supportsDisplayModes: true
285
+ }),
286
+ canHandle: function() {
287
+ if (typeof window === 'undefined') return false;
288
+ // Check for window.openai.callTool (the actual OpenAI SDK API)
289
+ if (window.openai && typeof window.openai.callTool === 'function') return true;
290
+ // Also check if we're being injected with tool metadata (OpenAI injects toolOutput)
291
+ if (window.openai && (window.openai.toolOutput !== undefined || window.openai.toolInput !== undefined)) return true;
292
+ return false;
293
+ },
294
+ initialize: function(context) {
295
+ var sdk = window.openai;
296
+ context.sdk = sdk;
297
+ // OpenAI SDK exposes theme and displayMode directly as properties
298
+ if (sdk.theme) {
299
+ context.hostContext.theme = sdk.theme;
300
+ }
301
+ if (sdk.displayMode) {
302
+ context.hostContext.displayMode = sdk.displayMode;
303
+ }
304
+ // Note: OpenAI SDK does not have an onContextChange equivalent
305
+ return Promise.resolve();
306
+ },
307
+ callTool: function(context, name, args) {
308
+ return context.sdk.callTool(name, args);
309
+ },
310
+ sendMessage: function(context, content) {
311
+ if (typeof context.sdk.sendFollowUpMessage === 'function') {
312
+ return context.sdk.sendFollowUpMessage(content);
313
+ }
314
+ return Promise.reject(new Error('Messages not supported'));
315
+ },
316
+ openLink: function(context, url) {
317
+ window.open(url, '_blank', 'noopener,noreferrer');
318
+ return Promise.resolve();
319
+ },
320
+ requestDisplayMode: function(context, mode) {
321
+ return Promise.resolve();
322
+ },
323
+ requestClose: function(context) {
324
+ return Promise.resolve();
325
+ }
326
+ };
327
+ `.trim();
328
+ }
329
+ function generateExtAppsAdapter(trustedOrigins) {
330
+ const originsArray = trustedOrigins.length > 0 ? JSON.stringify(trustedOrigins) : "[]";
331
+ return `
332
+ var ExtAppsAdapter = {
333
+ id: 'ext-apps',
334
+ name: 'ext-apps (SEP-1865)',
335
+ priority: 80,
336
+ capabilities: Object.assign({}, DEFAULT_CAPABILITIES, { canPersistState: true, hasNetworkAccess: true }),
337
+ trustedOrigins: ${originsArray},
338
+ trustedOrigin: null,
339
+ originTrustPending: false,
340
+ pendingRequests: {},
341
+ requestId: 0,
342
+ hostCapabilities: {},
343
+ canHandle: function() {
344
+ if (typeof window === 'undefined') return false;
345
+ if (window.parent === window) return false;
346
+
347
+ // Check for OpenAI SDK - defer to OpenAIAdapter
348
+ if (window.openai && window.openai.canvas) return false;
349
+ if (window.openai && typeof window.openai.callTool === 'function') return false;
350
+
351
+ // Explicit ext-apps marker
352
+ if (window.__mcpPlatform === 'ext-apps') return true;
353
+ if (window.__extAppsInitialized) return true;
354
+
355
+ // Claude MCP Apps mode (2026+) - uses ext-apps protocol
356
+ if (window.__mcpAppsEnabled) return true;
357
+
358
+ // Legacy Claude detection - defer to ClaudeAdapter
359
+ if (window.claude) return false;
360
+ if (window.__claudeArtifact) return false;
361
+ if (window.__mcpPlatform === 'claude') return false;
362
+ if (typeof location !== 'undefined') {
363
+ try {
364
+ var url = new URL(location.href);
365
+ var hostname = url.hostname.toLowerCase();
366
+ var isClaudeHost = hostname === 'claude.ai' || (hostname.length > 10 && hostname.slice(-10) === '.claude.ai');
367
+ var isAnthropicHost = hostname === 'anthropic.com' || (hostname.length > 14 && hostname.slice(-14) === '.anthropic.com');
368
+ if (isClaudeHost || isAnthropicHost) return false;
369
+ } catch (e) {
370
+ // If URL parsing fails, fall through to other checks
371
+ }
372
+ }
373
+
374
+ // Do NOT default to true for any iframe
375
+ return false;
376
+ },
377
+ initialize: function(context) {
378
+ var self = this;
379
+ context.extApps = this;
380
+
381
+ window.addEventListener('message', function(event) {
382
+ self.handleMessage(context, event);
383
+ });
384
+
385
+ return self.performHandshake(context);
386
+ },
387
+ handleMessage: function(context, event) {
388
+ if (!this.isOriginTrusted(event.origin)) return;
389
+ var data = event.data;
390
+ if (!data || typeof data !== 'object' || data.jsonrpc !== '2.0') return;
391
+
392
+ if ('id' in data && (data.result !== undefined || data.error !== undefined)) {
393
+ var pending = this.pendingRequests[data.id];
394
+ if (pending) {
395
+ clearTimeout(pending.timeout);
396
+ delete this.pendingRequests[data.id];
397
+ if (data.error) {
398
+ pending.reject(new Error(data.error.message + ' (code: ' + data.error.code + ')'));
399
+ } else {
400
+ pending.resolve(data.result);
401
+ }
402
+ }
403
+ return;
404
+ }
405
+
406
+ if ('method' in data && !('id' in data)) {
407
+ this.handleNotification(context, data);
408
+ }
409
+ },
410
+ handleNotification: function(context, notification) {
411
+ var params = notification.params || {};
412
+ switch (notification.method) {
413
+ case 'ui/notifications/tool-input':
414
+ context.toolInput = params.arguments || {};
415
+ window.dispatchEvent(new CustomEvent('tool:input', { detail: { arguments: context.toolInput } }));
416
+ break;
417
+ case 'ui/notifications/tool-input-partial':
418
+ // Streaming: merge partial input with existing
419
+ context.toolInput = Object.assign({}, context.toolInput, params.arguments || {});
420
+ window.dispatchEvent(new CustomEvent('tool:input-partial', { detail: { arguments: context.toolInput } }));
421
+ break;
422
+ case 'ui/notifications/tool-result':
423
+ context.toolOutput = params.content;
424
+ context.structuredContent = params.structuredContent;
425
+ context.notifyToolResult(params.content);
426
+ window.dispatchEvent(new CustomEvent('tool:result', { detail: params }));
427
+ break;
428
+ case 'ui/notifications/host-context-changed':
429
+ Object.assign(context.hostContext, params);
430
+ context.notifyContextChange(params);
431
+ break;
432
+ case 'ui/notifications/cancelled':
433
+ window.dispatchEvent(new CustomEvent('tool:cancelled', { detail: { reason: params.reason } }));
434
+ break;
435
+ }
436
+ },
437
+ isOriginTrusted: function(origin) {
438
+ if (this.trustedOrigins.length > 0) {
439
+ return this.trustedOrigins.indexOf(origin) !== -1;
440
+ }
441
+ // Trust-on-first-use: trust first message origin.
442
+ // SECURITY WARNING: For production, always configure trustedOrigins.
443
+ if (!this.trustedOrigin) {
444
+ // Guard against race condition where multiple messages arrive simultaneously
445
+ if (this.originTrustPending) {
446
+ return false;
447
+ }
448
+ if (window.parent !== window && origin) {
449
+ this.originTrustPending = true;
450
+ this.trustedOrigin = origin;
451
+ this.originTrustPending = false; // Reset after successful trust establishment
452
+ return true;
453
+ }
454
+ return false;
455
+ }
456
+ return this.trustedOrigin === origin;
457
+ },
458
+ sendRequest: function(method, params) {
459
+ var self = this;
460
+ return new Promise(function(resolve, reject) {
461
+ // Security: Require trusted origin before sending requests to prevent message leaks
462
+ if (!self.trustedOrigin && self.trustedOrigins.length === 0) {
463
+ reject(new Error('Cannot send request: no trusted origin established'));
464
+ return;
465
+ }
466
+
467
+ var id = ++self.requestId;
468
+ var timeout = setTimeout(function() {
469
+ delete self.pendingRequests[id];
470
+ reject(new Error('Request ' + method + ' timed out'));
471
+ }, 10000);
472
+
473
+ self.pendingRequests[id] = { resolve: resolve, reject: reject, timeout: timeout };
474
+
475
+ var targetOrigin = self.trustedOrigin || self.trustedOrigins[0];
476
+ window.parent.postMessage({ jsonrpc: '2.0', id: id, method: method, params: params }, targetOrigin);
477
+ });
478
+ },
479
+ performHandshake: function(context) {
480
+ var self = this;
481
+ var params = {
482
+ appInfo: { name: 'FrontMCP Widget', version: '1.0.0' },
483
+ appCapabilities: { tools: { listChanged: false } },
484
+ protocolVersion: '2024-11-05'
485
+ };
486
+
487
+ return this.sendRequest('ui/initialize', params).then(function(result) {
488
+ self.hostCapabilities = result.hostCapabilities || {};
489
+ self.capabilities = Object.assign({}, self.capabilities, {
490
+ canCallTools: Boolean(self.hostCapabilities.serverToolProxy),
491
+ canSendMessages: true,
492
+ canOpenLinks: Boolean(self.hostCapabilities.openLink),
493
+ supportsDisplayModes: true
494
+ });
495
+ if (result.hostContext) {
496
+ Object.assign(context.hostContext, result.hostContext);
497
+ }
498
+ });
499
+ },
500
+ callTool: function(context, name, args) {
501
+ if (!this.hostCapabilities.serverToolProxy) {
502
+ return Promise.reject(new Error('Server tool proxy not supported'));
503
+ }
504
+ return this.sendRequest('ui/callServerTool', { name: name, arguments: args });
505
+ },
506
+ sendMessage: function(context, content) {
507
+ return this.sendRequest('ui/message', { content: content });
508
+ },
509
+ openLink: function(context, url) {
510
+ if (!this.hostCapabilities.openLink) {
511
+ window.open(url, '_blank', 'noopener,noreferrer');
512
+ return Promise.resolve();
513
+ }
514
+ return this.sendRequest('ui/openLink', { url: url });
515
+ },
516
+ requestDisplayMode: function(context, mode) {
517
+ return this.sendRequest('ui/setDisplayMode', { mode: mode });
518
+ },
519
+ requestClose: function(context) {
520
+ return this.sendRequest('ui/close', {});
521
+ },
522
+ // Extended ext-apps methods (full specification)
523
+ updateModelContext: function(context, data, merge) {
524
+ if (!this.hostCapabilities.modelContextUpdate) {
525
+ return Promise.reject(new Error('Model context update not supported'));
526
+ }
527
+ return this.sendRequest('ui/updateModelContext', { context: data, merge: merge !== false });
528
+ },
529
+ log: function(context, level, message, data) {
530
+ if (!this.hostCapabilities.logging) {
531
+ // Fallback to console logging if host doesn't support it
532
+ var logFn = console[level] || console.log;
533
+ logFn('[Widget] ' + message, data);
534
+ return Promise.resolve();
535
+ }
536
+ return this.sendRequest('ui/log', { level: level, message: message, data: data });
537
+ },
538
+ registerTool: function(context, name, description, inputSchema) {
539
+ if (!this.hostCapabilities.widgetTools) {
540
+ return Promise.reject(new Error('Widget tool registration not supported'));
541
+ }
542
+ return this.sendRequest('ui/registerTool', { name: name, description: description, inputSchema: inputSchema });
543
+ },
544
+ unregisterTool: function(context, name) {
545
+ if (!this.hostCapabilities.widgetTools) {
546
+ return Promise.reject(new Error('Widget tool unregistration not supported'));
547
+ }
548
+ return this.sendRequest('ui/unregisterTool', { name: name });
549
+ }
550
+ };
551
+ `.trim();
552
+ }
553
+ function generateClaudeAdapter() {
554
+ return `
555
+ var ClaudeAdapter = {
556
+ id: 'claude',
557
+ name: 'Claude (Anthropic)',
558
+ priority: 60,
559
+ capabilities: Object.assign({}, DEFAULT_CAPABILITIES, {
560
+ canCallTools: false,
561
+ canSendMessages: false,
562
+ canOpenLinks: true,
563
+ hasNetworkAccess: false,
564
+ supportsDisplayModes: false
565
+ }),
566
+ canHandle: function() {
567
+ if (typeof window === 'undefined') return false;
568
+
569
+ // If MCP Apps is enabled, let ext-apps adapter handle it
570
+ if (window.__mcpAppsEnabled) return false;
571
+ if (window.__mcpPlatform === 'ext-apps') return false;
572
+ if (window.__extAppsInitialized) return false;
573
+
574
+ // Legacy Claude detection
575
+ if (window.__mcpPlatform === 'claude') return true;
576
+ if (window.claude) return true;
577
+ if (window.__claudeArtifact) return true;
578
+ if (typeof location !== 'undefined') {
579
+ try {
580
+ var url = new URL(location.href);
581
+ var hostname = url.hostname.toLowerCase();
582
+ var isClaudeHost = hostname === 'claude.ai' || (hostname.length > 10 && hostname.slice(-10) === '.claude.ai');
583
+ var isAnthropicHost = hostname === 'anthropic.com' || (hostname.length > 14 && hostname.slice(-14) === '.anthropic.com');
584
+ if (isClaudeHost || isAnthropicHost) return true;
585
+ } catch (e) {
586
+ // If URL parsing fails, fall through
587
+ }
588
+ }
589
+ return false;
590
+ },
591
+ initialize: function(context) {
592
+ return Promise.resolve();
593
+ },
594
+ callTool: function() {
595
+ return Promise.reject(new Error('Tool calls not supported in Claude'));
596
+ },
597
+ sendMessage: function() {
598
+ return Promise.reject(new Error('Messages not supported in Claude'));
599
+ },
600
+ openLink: function(context, url) {
601
+ window.open(url, '_blank', 'noopener,noreferrer');
602
+ return Promise.resolve();
603
+ },
604
+ requestDisplayMode: function() {
605
+ return Promise.resolve();
606
+ },
607
+ requestClose: function() {
608
+ return Promise.resolve();
609
+ }
610
+ };
611
+ `.trim();
612
+ }
613
+ function generateGeminiAdapter() {
614
+ return `
615
+ var GeminiAdapter = {
616
+ id: 'gemini',
617
+ name: 'Google Gemini',
618
+ priority: 40,
619
+ capabilities: Object.assign({}, DEFAULT_CAPABILITIES, {
620
+ canOpenLinks: true,
621
+ hasNetworkAccess: true
622
+ }),
623
+ canHandle: function() {
624
+ if (typeof window === 'undefined') return false;
625
+ if (window.__mcpPlatform === 'gemini') return true;
626
+ if (window.gemini) return true;
627
+ if (typeof location !== 'undefined') {
628
+ var href = location.href;
629
+ if (href.indexOf('gemini.google.com') !== -1 || href.indexOf('bard.google.com') !== -1) return true;
630
+ }
631
+ return false;
632
+ },
633
+ initialize: function(context) {
634
+ if (window.gemini && window.gemini.ui && window.gemini.ui.getTheme) {
635
+ context.hostContext.theme = window.gemini.ui.getTheme() === 'dark' ? 'dark' : 'light';
636
+ }
637
+ return Promise.resolve();
638
+ },
639
+ callTool: function() {
640
+ return Promise.reject(new Error('Tool calls not supported in Gemini'));
641
+ },
642
+ sendMessage: function(context, content) {
643
+ if (window.gemini && window.gemini.ui && window.gemini.ui.sendMessage) {
644
+ return window.gemini.ui.sendMessage(content);
645
+ }
646
+ return Promise.reject(new Error('Messages not supported in Gemini'));
647
+ },
648
+ openLink: function(context, url) {
649
+ if (window.gemini && window.gemini.ui && window.gemini.ui.openLink) {
650
+ return window.gemini.ui.openLink(url);
651
+ }
652
+ window.open(url, '_blank', 'noopener,noreferrer');
653
+ return Promise.resolve();
654
+ },
655
+ requestDisplayMode: function() {
656
+ return Promise.resolve();
657
+ },
658
+ requestClose: function() {
659
+ return Promise.resolve();
660
+ }
661
+ };
662
+ `.trim();
663
+ }
664
+ function generateGenericAdapter() {
665
+ return `
666
+ var GenericAdapter = {
667
+ id: 'generic',
668
+ name: 'Generic Web',
669
+ priority: 0,
670
+ capabilities: Object.assign({}, DEFAULT_CAPABILITIES, {
671
+ canOpenLinks: true,
672
+ hasNetworkAccess: true
673
+ }),
674
+ canHandle: function() {
675
+ return typeof window !== 'undefined';
676
+ },
677
+ initialize: function(context) {
678
+ return Promise.resolve();
679
+ },
680
+ callTool: function() {
681
+ return Promise.reject(new Error('Tool calls not supported'));
682
+ },
683
+ sendMessage: function() {
684
+ return Promise.reject(new Error('Messages not supported'));
685
+ },
686
+ openLink: function(context, url) {
687
+ window.open(url, '_blank', 'noopener,noreferrer');
688
+ return Promise.resolve();
689
+ },
690
+ requestDisplayMode: function() {
691
+ return Promise.resolve();
692
+ },
693
+ requestClose: function() {
694
+ return Promise.resolve();
695
+ }
696
+ };
697
+ `.trim();
698
+ }
699
+ function generatePlatformDetection(adapters) {
700
+ const adapterVars = adapters.map((a) => {
701
+ switch (a) {
702
+ case "openai":
703
+ return "OpenAIAdapter";
704
+ case "ext-apps":
705
+ return "ExtAppsAdapter";
706
+ case "claude":
707
+ return "ClaudeAdapter";
708
+ case "gemini":
709
+ return "GeminiAdapter";
710
+ case "generic":
711
+ return "GenericAdapter";
712
+ default:
713
+ return "";
714
+ }
715
+ }).filter(Boolean);
716
+ return `
717
+ var ADAPTERS = [${adapterVars.join(", ")}].sort(function(a, b) { return b.priority - a.priority; });
718
+
719
+ function detectPlatform() {
720
+ for (var i = 0; i < ADAPTERS.length; i++) {
721
+ if (ADAPTERS[i].canHandle()) {
722
+ log('Detected platform: ' + ADAPTERS[i].id);
723
+ return ADAPTERS[i];
724
+ }
725
+ }
726
+ log('No platform detected, using generic');
727
+ return GenericAdapter;
728
+ }
729
+ `.trim();
730
+ }
731
+ function generateBridgeClass() {
732
+ return `
733
+ function FrontMcpBridge() {
734
+ this._adapter = null;
735
+ this._initialized = false;
736
+ this._context = {
737
+ hostContext: {
738
+ theme: detectTheme(),
739
+ displayMode: 'inline',
740
+ locale: detectLocale(),
741
+ userAgent: detectUserAgent(),
742
+ safeArea: DEFAULT_SAFE_AREA,
743
+ viewport: detectViewport()
744
+ },
745
+ toolInput: {},
746
+ toolOutput: undefined,
747
+ structuredContent: undefined,
748
+ widgetState: {},
749
+ contextListeners: [],
750
+ toolResultListeners: [],
751
+ notifyContextChange: function(changes) {
752
+ Object.assign(this.hostContext, changes);
753
+ for (var i = 0; i < this.contextListeners.length; i++) {
754
+ try { this.contextListeners[i](changes); } catch(e) {}
755
+ }
756
+ },
757
+ notifyToolResult: function(result) {
758
+ this.toolOutput = result;
759
+ for (var i = 0; i < this.toolResultListeners.length; i++) {
760
+ try { this.toolResultListeners[i](result); } catch(e) {}
761
+ }
762
+ }
763
+ };
764
+
765
+ var injected = readInjectedData();
766
+ this._context.toolInput = injected.toolInput;
767
+ this._context.toolOutput = injected.toolOutput;
768
+ this._context.structuredContent = injected.structuredContent;
769
+
770
+ this._loadWidgetState();
771
+ }
772
+
773
+ FrontMcpBridge.prototype._loadWidgetState = function() {
774
+ try {
775
+ var key = 'frontmcp:widget:' + (window.__mcpToolName || 'unknown');
776
+ var stored = localStorage.getItem(key);
777
+ if (stored) this._context.widgetState = JSON.parse(stored);
778
+ } catch(e) {}
779
+ };
780
+
781
+ FrontMcpBridge.prototype._saveWidgetState = function() {
782
+ try {
783
+ var key = 'frontmcp:widget:' + (window.__mcpToolName || 'unknown');
784
+ localStorage.setItem(key, JSON.stringify(this._context.widgetState));
785
+ } catch(e) {}
786
+ };
787
+
788
+ FrontMcpBridge.prototype.initialize = function() {
789
+ if (this._initialized) return Promise.resolve();
790
+ var self = this;
791
+ this._adapter = detectPlatform();
792
+ return this._adapter.initialize(this._context).then(function() {
793
+ self._initialized = true;
794
+ // Set up the data-tool-call click handler after initialization
795
+ self._setupDataToolCallHandler();
796
+ });
797
+ };
798
+
799
+ Object.defineProperty(FrontMcpBridge.prototype, 'initialized', { get: function() { return this._initialized; } });
800
+ Object.defineProperty(FrontMcpBridge.prototype, 'adapterId', { get: function() { return this._adapter ? this._adapter.id : undefined; } });
801
+ Object.defineProperty(FrontMcpBridge.prototype, 'capabilities', { get: function() { return this._adapter ? this._adapter.capabilities : DEFAULT_CAPABILITIES; } });
802
+
803
+ FrontMcpBridge.prototype.getTheme = function() { return this._context.hostContext.theme; };
804
+ FrontMcpBridge.prototype.getDisplayMode = function() { return this._context.hostContext.displayMode; };
805
+ FrontMcpBridge.prototype.getToolInput = function() { return this._context.toolInput; };
806
+ FrontMcpBridge.prototype.getToolOutput = function() { return this._context.toolOutput; };
807
+ FrontMcpBridge.prototype.getStructuredContent = function() { return this._context.structuredContent; };
808
+ FrontMcpBridge.prototype.getWidgetState = function() { return this._context.widgetState; };
809
+ FrontMcpBridge.prototype.getHostContext = function() { return Object.assign({}, this._context.hostContext); };
810
+ FrontMcpBridge.prototype.hasCapability = function(cap) { return this._adapter && this._adapter.capabilities[cap] === true; };
811
+
812
+ // Get tool response metadata (platform-agnostic)
813
+ // Used by inline mode widgets to detect when ui/html arrives
814
+ FrontMcpBridge.prototype.getToolResponseMetadata = function() {
815
+ // OpenAI injects toolResponseMetadata for widget-producing tools
816
+ if (typeof window !== 'undefined' && window.openai && window.openai.toolResponseMetadata) {
817
+ return window.openai.toolResponseMetadata;
818
+ }
819
+ // Claude (future support)
820
+ if (typeof window !== 'undefined' && window.claude && window.claude.toolResponseMetadata) {
821
+ return window.claude.toolResponseMetadata;
822
+ }
823
+ // FrontMCP direct injection (for testing/ext-apps)
824
+ if (typeof window !== 'undefined' && window.__mcpToolResponseMetadata) {
825
+ return window.__mcpToolResponseMetadata;
826
+ }
827
+ return null;
828
+ };
829
+
830
+ // Subscribe to tool response metadata changes (for inline mode injection)
831
+ FrontMcpBridge.prototype.onToolResponseMetadata = function(callback) {
832
+ var self = this;
833
+ var called = false;
834
+
835
+ // Check if already available
836
+ var existing = self.getToolResponseMetadata();
837
+ if (existing) {
838
+ called = true;
839
+ callback(existing);
840
+ }
841
+
842
+ // Set up property interceptors for OpenAI
843
+ if (typeof window !== 'undefined') {
844
+ // OpenAI: Intercept toolResponseMetadata assignment
845
+ if (!window.__frontmcpMetadataIntercepted) {
846
+ window.__frontmcpMetadataIntercepted = true;
847
+ window.__frontmcpMetadataCallbacks = [];
848
+
849
+ // Create openai object if it doesn't exist
850
+ if (!window.openai) window.openai = {};
851
+
852
+ var originalMetadata = window.openai.toolResponseMetadata;
853
+ Object.defineProperty(window.openai, 'toolResponseMetadata', {
854
+ get: function() { return originalMetadata; },
855
+ set: function(val) {
856
+ originalMetadata = val;
857
+ log('toolResponseMetadata set, notifying ' + window.__frontmcpMetadataCallbacks.length + ' listeners');
858
+ for (var i = 0; i < window.__frontmcpMetadataCallbacks.length; i++) {
859
+ try { window.__frontmcpMetadataCallbacks[i](val); } catch(e) {}
860
+ }
861
+ },
862
+ configurable: true
863
+ });
864
+ }
865
+
866
+ // Register callback wrapper (store reference for unsubscribe)
867
+ var wrapper = function(metadata) {
868
+ if (!called) {
869
+ called = true;
870
+ callback(metadata);
871
+ }
872
+ };
873
+ window.__frontmcpMetadataCallbacks.push(wrapper);
874
+
875
+ // Return unsubscribe function that removes the wrapper (not the original callback)
876
+ return function() {
877
+ if (window.__frontmcpMetadataCallbacks) {
878
+ var idx = window.__frontmcpMetadataCallbacks.indexOf(wrapper);
879
+ if (idx !== -1) window.__frontmcpMetadataCallbacks.splice(idx, 1);
880
+ }
881
+ };
882
+ }
883
+
884
+ // Return no-op unsubscribe for non-window environments
885
+ return function() {};
886
+ };
887
+
888
+ FrontMcpBridge.prototype.callTool = function(name, args) {
889
+ // Priority 1: Direct OpenAI SDK call (most reliable in OpenAI iframe)
890
+ // This bypasses adapter abstraction for maximum compatibility
891
+ if (typeof window !== 'undefined' && window.openai && typeof window.openai.callTool === 'function') {
892
+ log('callTool: Using OpenAI SDK directly');
893
+ return window.openai.callTool(name, args);
894
+ }
895
+
896
+ // Priority 2: Use adapter (if initialized and supports tool calls)
897
+ if (this._adapter && this._adapter.capabilities && this._adapter.capabilities.canCallTools) {
898
+ log('callTool: Using adapter ' + this._adapter.id);
899
+ return this._adapter.callTool(this._context, name, args);
900
+ }
901
+
902
+ // Not initialized or no tool support
903
+ if (!this._adapter) {
904
+ return Promise.reject(new Error('Bridge not initialized. Wait for bridge:ready event.'));
905
+ }
906
+ return Promise.reject(new Error('Tool calls not supported on this platform (' + this._adapter.id + ')'));
907
+ };
908
+
909
+ FrontMcpBridge.prototype.sendMessage = function(content) {
910
+ if (!this._adapter) return Promise.reject(new Error('Not initialized'));
911
+ return this._adapter.sendMessage(this._context, content);
912
+ };
913
+
914
+ FrontMcpBridge.prototype.openLink = function(url) {
915
+ if (!this._adapter) return Promise.reject(new Error('Not initialized'));
916
+ return this._adapter.openLink(this._context, url);
917
+ };
918
+
919
+ FrontMcpBridge.prototype.requestDisplayMode = function(mode) {
920
+ if (!this._adapter) return Promise.reject(new Error('Not initialized'));
921
+ var self = this;
922
+ return this._adapter.requestDisplayMode(this._context, mode).then(function() {
923
+ self._context.hostContext.displayMode = mode;
924
+ });
925
+ };
926
+
927
+ FrontMcpBridge.prototype.requestClose = function() {
928
+ if (!this._adapter) return Promise.reject(new Error('Not initialized'));
929
+ return this._adapter.requestClose(this._context);
930
+ };
931
+
932
+ // Extended ext-apps methods (full specification)
933
+ FrontMcpBridge.prototype.updateModelContext = function(context, merge) {
934
+ if (!this._adapter) return Promise.reject(new Error('Not initialized'));
935
+ if (!this._adapter.updateModelContext) {
936
+ return Promise.reject(new Error('updateModelContext not supported on this platform'));
937
+ }
938
+ return this._adapter.updateModelContext(this._context, context, merge);
939
+ };
940
+
941
+ FrontMcpBridge.prototype.log = function(level, message, data) {
942
+ if (!this._adapter) return Promise.reject(new Error('Not initialized'));
943
+ if (!this._adapter.log) {
944
+ // Fallback to console
945
+ var logFn = console[level] || console.log;
946
+ logFn('[Widget] ' + message, data);
947
+ return Promise.resolve();
948
+ }
949
+ return this._adapter.log(this._context, level, message, data);
950
+ };
951
+
952
+ FrontMcpBridge.prototype.registerTool = function(name, description, inputSchema) {
953
+ if (!this._adapter) return Promise.reject(new Error('Not initialized'));
954
+ if (!this._adapter.registerTool) {
955
+ return Promise.reject(new Error('registerTool not supported on this platform'));
956
+ }
957
+ return this._adapter.registerTool(this._context, name, description, inputSchema);
958
+ };
959
+
960
+ FrontMcpBridge.prototype.unregisterTool = function(name) {
961
+ if (!this._adapter) return Promise.reject(new Error('Not initialized'));
962
+ if (!this._adapter.unregisterTool) {
963
+ return Promise.reject(new Error('unregisterTool not supported on this platform'));
964
+ }
965
+ return this._adapter.unregisterTool(this._context, name);
966
+ };
967
+
968
+ FrontMcpBridge.prototype.setWidgetState = function(state) {
969
+ Object.assign(this._context.widgetState, state);
970
+ this._saveWidgetState();
971
+ };
972
+
973
+ FrontMcpBridge.prototype.onContextChange = function(callback) {
974
+ var listeners = this._context.contextListeners;
975
+ listeners.push(callback);
976
+ return function() {
977
+ var idx = listeners.indexOf(callback);
978
+ if (idx !== -1) listeners.splice(idx, 1);
979
+ };
980
+ };
981
+
982
+ FrontMcpBridge.prototype.onToolResult = function(callback) {
983
+ var listeners = this._context.toolResultListeners;
984
+ listeners.push(callback);
985
+ return function() {
986
+ var idx = listeners.indexOf(callback);
987
+ if (idx !== -1) listeners.splice(idx, 1);
988
+ };
989
+ };
990
+
991
+ // ==================== data-tool-call Click Handler ====================
992
+
993
+ FrontMcpBridge.prototype._setupDataToolCallHandler = function() {
994
+ var self = this;
995
+
996
+ document.addEventListener('click', function(e) {
997
+ // Find the closest element with data-tool-call attribute
998
+ var target = e.target;
999
+ while (target && target !== document) {
1000
+ if (target.hasAttribute && target.hasAttribute('data-tool-call')) {
1001
+ var toolName = target.getAttribute('data-tool-call');
1002
+ var argsAttr = target.getAttribute('data-tool-args');
1003
+ var args = {};
1004
+
1005
+ try {
1006
+ if (argsAttr) {
1007
+ args = JSON.parse(argsAttr);
1008
+ }
1009
+ } catch (parseErr) {
1010
+ console.error('[frontmcp] Failed to parse data-tool-args:', parseErr);
1011
+ }
1012
+
1013
+ log('data-tool-call clicked: ' + toolName);
1014
+
1015
+ // Show loading state - save original content first
1016
+ var originalContent = target.innerHTML;
1017
+ var originalDisabled = target.disabled;
1018
+ target.disabled = true;
1019
+ target.classList.add('opacity-50', 'cursor-not-allowed');
1020
+
1021
+ // Add spinner for buttons
1022
+ 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>';
1023
+ if (target.tagName === 'BUTTON') {
1024
+ target.innerHTML = spinner + 'Loading...';
1025
+ }
1026
+
1027
+ // Helper to reset button state
1028
+ function resetButton() {
1029
+ target.innerHTML = originalContent;
1030
+ target.disabled = originalDisabled;
1031
+ target.classList.remove('opacity-50', 'cursor-not-allowed');
1032
+ }
1033
+
1034
+ // Determine how to call the tool
1035
+ var toolCallPromise;
1036
+
1037
+ // Priority 1: Direct OpenAI SDK call (bypasses adapter abstraction)
1038
+ if (typeof window !== 'undefined' && window.openai && typeof window.openai.callTool === 'function') {
1039
+ log('Using OpenAI SDK directly for tool call');
1040
+ toolCallPromise = window.openai.callTool(toolName, args);
1041
+ }
1042
+ // Priority 2: Use adapter (if it supports tool calls)
1043
+ else if (self.hasCapability('canCallTools')) {
1044
+ log('Using adapter for tool call');
1045
+ toolCallPromise = self.callTool(toolName, args);
1046
+ }
1047
+ // No tool call capability
1048
+ else {
1049
+ console.error('[frontmcp] Tool calls not supported on this platform (' + self.adapterId + ')');
1050
+ resetButton();
1051
+ target.dispatchEvent(new CustomEvent('tool:error', {
1052
+ detail: { name: toolName, args: args, error: 'Tool calls not supported on this platform' },
1053
+ bubbles: true
1054
+ }));
1055
+ e.preventDefault();
1056
+ return;
1057
+ }
1058
+
1059
+ // Handle the tool call result
1060
+ toolCallPromise.then(function(result) {
1061
+ log('Tool call succeeded: ' + toolName);
1062
+ resetButton();
1063
+
1064
+ // Update bridge state to trigger widget re-render
1065
+ // React isn't hydrated in OpenAI iframe, so useState doesn't work
1066
+ // Instead, we use the bridge's reactive state system
1067
+ if (result && window.__frontmcp && window.__frontmcp.bridge && typeof window.__frontmcp.bridge.setWidgetState === 'function') {
1068
+ var newData = result.structuredContent || result;
1069
+ log('Updating bridge state with new data');
1070
+ window.__frontmcp.bridge.setWidgetState(newData);
1071
+ }
1072
+
1073
+ // Dispatch success event
1074
+ target.dispatchEvent(new CustomEvent('tool:success', {
1075
+ detail: { name: toolName, args: args, result: result },
1076
+ bubbles: true
1077
+ }));
1078
+ }).catch(function(err) {
1079
+ console.error('[frontmcp] Tool call failed: ' + toolName, err);
1080
+ resetButton();
1081
+ // Dispatch error event
1082
+ target.dispatchEvent(new CustomEvent('tool:error', {
1083
+ detail: { name: toolName, args: args, error: err.message || err },
1084
+ bubbles: true
1085
+ }));
1086
+ });
1087
+
1088
+ // Prevent default behavior (e.g., form submission)
1089
+ e.preventDefault();
1090
+ return;
1091
+ }
1092
+ target = target.parentElement;
1093
+ }
1094
+ }, true); // Use capture phase to handle before React handlers
1095
+ };
1096
+ `.trim();
1097
+ }
1098
+ var MAX_MINIFY_CODE_LENGTH = 5e5;
1099
+ function minifyJS(code) {
1100
+ if (code.length > MAX_MINIFY_CODE_LENGTH) {
1101
+ return code;
1102
+ }
1103
+ return code.replace(/\/\*[\s\S]*?\*\//g, "").replace(/(^|[^:])\/\/.*$/gm, "$1").replace(/\s+/g, " ").replace(/\s*([{};,:()[\]])\s*/g, "$1").replace(/;\}/g, "}").trim();
1104
+ }
1105
+ function generatePlatformBundle(platform, options = {}) {
1106
+ const platformAdapters = {
1107
+ chatgpt: ["openai", "generic"],
1108
+ claude: ["claude", "generic"],
1109
+ gemini: ["gemini", "generic"],
1110
+ universal: ["openai", "ext-apps", "claude", "gemini", "generic"]
1111
+ };
1112
+ return generateBridgeIIFE({
1113
+ ...options,
1114
+ adapters: platformAdapters[platform]
1115
+ });
1116
+ }
1117
+ var UNIVERSAL_BRIDGE_SCRIPT = generateBridgeIIFE();
1118
+ var BRIDGE_SCRIPT_TAGS = {
1119
+ universal: `<script>${UNIVERSAL_BRIDGE_SCRIPT}</script>`,
1120
+ chatgpt: `<script>${generatePlatformBundle("chatgpt")}</script>`,
1121
+ claude: `<script>${generatePlatformBundle("claude")}</script>`,
1122
+ gemini: `<script>${generatePlatformBundle("gemini")}</script>`
1123
+ };
1124
+
1125
+ // libs/uipack/src/shell/custom-shell-types.ts
1126
+ var SHELL_PLACEHOLDER_NAMES = ["CSP", "DATA", "BRIDGE", "CONTENT", "TITLE"];
1127
+ var SHELL_PLACEHOLDERS = {
1128
+ CSP: "{{CSP}}",
1129
+ DATA: "{{DATA}}",
1130
+ BRIDGE: "{{BRIDGE}}",
1131
+ CONTENT: "{{CONTENT}}",
1132
+ TITLE: "{{TITLE}}"
1133
+ };
1134
+ var REQUIRED_PLACEHOLDERS = ["CONTENT"];
1135
+ var OPTIONAL_PLACEHOLDERS = ["CSP", "DATA", "BRIDGE", "TITLE"];
1136
+ function isInlineShellSource(source) {
1137
+ return typeof source === "object" && source !== null && "inline" in source;
1138
+ }
1139
+ function isUrlShellSource(source) {
1140
+ return typeof source === "object" && source !== null && "url" in source;
1141
+ }
1142
+ function isNpmShellSource(source) {
1143
+ return typeof source === "object" && source !== null && "npm" in source;
1144
+ }
1145
+
1146
+ // libs/uipack/src/shell/custom-shell-validator.ts
1147
+ function validateShellTemplate(template) {
1148
+ const found = {};
1149
+ for (const name of SHELL_PLACEHOLDER_NAMES) {
1150
+ found[name] = template.includes(SHELL_PLACEHOLDERS[name]);
1151
+ }
1152
+ const missingRequired = REQUIRED_PLACEHOLDERS.filter((name) => !found[name]);
1153
+ const missingOptional = OPTIONAL_PLACEHOLDERS.filter((name) => !found[name]);
1154
+ return {
1155
+ valid: missingRequired.length === 0,
1156
+ found,
1157
+ missingRequired,
1158
+ missingOptional
1159
+ };
1160
+ }
1161
+
1162
+ // libs/uipack/src/shell/custom-shell-applier.ts
1163
+ function applyShellTemplate(template, values) {
1164
+ let result = template;
1165
+ result = result.replaceAll(SHELL_PLACEHOLDERS.CSP, values.csp);
1166
+ result = result.replaceAll(SHELL_PLACEHOLDERS.DATA, values.data);
1167
+ result = result.replaceAll(SHELL_PLACEHOLDERS.BRIDGE, values.bridge);
1168
+ result = result.replaceAll(SHELL_PLACEHOLDERS.CONTENT, values.content);
1169
+ result = result.replaceAll(SHELL_PLACEHOLDERS.TITLE, values.title);
1170
+ return result;
1171
+ }
1172
+
1173
+ // libs/uipack/src/shell/builder.ts
1174
+ function buildShell(content, config) {
1175
+ const { toolName, csp, withShell = true, input, output, structuredContent, includeBridge = true, title } = config;
1176
+ const { customShell } = config;
1177
+ const dataScript = buildDataInjectionScript({ toolName, input, output, structuredContent });
1178
+ if (!withShell) {
1179
+ const html2 = `${dataScript}
1180
+ ${content}`;
1181
+ return {
1182
+ html: html2,
1183
+ hash: simpleHash(html2),
1184
+ size: Buffer.byteLength(html2, "utf-8")
1185
+ };
1186
+ }
1187
+ if (customShell) {
1188
+ return buildCustomShell(content, customShell, {
1189
+ csp,
1190
+ toolName,
1191
+ input,
1192
+ output,
1193
+ structuredContent,
1194
+ includeBridge,
1195
+ title
1196
+ });
1197
+ }
1198
+ const headParts = [
1199
+ '<meta charset="UTF-8">',
1200
+ '<meta name="viewport" content="width=device-width, initial-scale=1.0">'
1201
+ ];
1202
+ if (title) {
1203
+ headParts.push(`<title>${escapeHtmlForTag(title)}</title>`);
1204
+ }
1205
+ headParts.push(buildCSPMetaTag(csp));
1206
+ headParts.push(dataScript);
1207
+ if (includeBridge) {
1208
+ const bridgeScript = generateBridgeIIFE({ minify: true });
1209
+ headParts.push(`<script>${bridgeScript}</script>`);
1210
+ }
1211
+ const html = `<!DOCTYPE html>
1212
+ <html lang="en">
1213
+ <head>
1214
+ ${headParts.map((p) => ` ${p}`).join("\n")}
1215
+ </head>
1216
+ <body>
1217
+ ${content}
1218
+ </body>
1219
+ </html>`;
1220
+ return {
1221
+ html,
1222
+ hash: simpleHash(html),
1223
+ size: Buffer.byteLength(html, "utf-8")
1224
+ };
1225
+ }
1226
+ function buildCustomShell(content, customShell, ctx) {
1227
+ let template;
1228
+ if (typeof customShell === "string") {
1229
+ const validation = validateShellTemplate(customShell);
1230
+ if (!validation.valid) {
1231
+ throw new Error(
1232
+ `Custom shell template is missing required placeholder(s): ${validation.missingRequired.map((n) => `{{${n}}}`).join(", ")}`
1233
+ );
1234
+ }
1235
+ template = customShell;
1236
+ } else {
1237
+ template = customShell.template;
1238
+ }
1239
+ const cspTag = buildCSPMetaTag(ctx.csp);
1240
+ const dataScript = buildDataInjectionScript({
1241
+ toolName: ctx.toolName,
1242
+ input: ctx.input,
1243
+ output: ctx.output,
1244
+ structuredContent: ctx.structuredContent
1245
+ });
1246
+ let bridgeHtml = "";
1247
+ if (ctx.includeBridge) {
1248
+ const bridgeScript = generateBridgeIIFE({ minify: true });
1249
+ bridgeHtml = `<script>${bridgeScript}</script>`;
1250
+ }
1251
+ const html = applyShellTemplate(template, {
1252
+ csp: cspTag,
1253
+ data: dataScript,
1254
+ bridge: bridgeHtml,
1255
+ content,
1256
+ title: ctx.title ? escapeHtmlForTag(ctx.title) : ""
1257
+ });
1258
+ return {
1259
+ html,
1260
+ hash: simpleHash(html),
1261
+ size: Buffer.byteLength(html, "utf-8")
1262
+ };
1263
+ }
1264
+ function simpleHash(str) {
1265
+ let hash = 0;
1266
+ for (let i = 0; i < str.length; i++) {
1267
+ const char = str.charCodeAt(i);
1268
+ hash = (hash << 5) - hash + char | 0;
1269
+ }
1270
+ return Math.abs(hash).toString(16).padStart(8, "0");
1271
+ }
1272
+ function escapeHtmlForTag(str) {
1273
+ return str.replace(/&/g, "&amp;").replace(/</g, "&lt;").replace(/>/g, "&gt;");
1274
+ }
1275
+
1276
+ // libs/uipack/src/shell/custom-shell-resolver.ts
1277
+ var DEFAULT_TIMEOUT_MS = 1e4;
1278
+ var templateCache = /* @__PURE__ */ new Map();
1279
+ async function resolveShellTemplate(source, options) {
1280
+ if (isInlineShellSource(source)) {
1281
+ return resolveInline(source.inline);
1282
+ }
1283
+ if (isUrlShellSource(source)) {
1284
+ const cacheKey = `url:${source.url}`;
1285
+ if (!options?.noCache) {
1286
+ const cached = templateCache.get(cacheKey);
1287
+ if (cached) return cached;
1288
+ }
1289
+ const template = await fetchWithTimeout(source.url, source.timeout ?? DEFAULT_TIMEOUT_MS);
1290
+ const resolved = buildResolved(template, "url");
1291
+ if (!options?.noCache) {
1292
+ templateCache.set(cacheKey, resolved);
1293
+ }
1294
+ return resolved;
1295
+ }
1296
+ if (isNpmShellSource(source)) {
1297
+ const specifier = source.version ? `${source.npm}@${source.version}` : source.npm;
1298
+ const cacheKey = `npm:${specifier}`;
1299
+ if (!options?.noCache) {
1300
+ const cached = templateCache.get(cacheKey);
1301
+ if (cached) return cached;
1302
+ }
1303
+ const template = await resolveNpmTemplate(source, specifier, options?.resolver);
1304
+ const resolved = buildResolved(template, "npm");
1305
+ if (!options?.noCache) {
1306
+ templateCache.set(cacheKey, resolved);
1307
+ }
1308
+ return resolved;
1309
+ }
1310
+ throw new Error('Invalid custom shell source: must have "inline", "url", or "npm" property');
1311
+ }
1312
+ function clearShellTemplateCache() {
1313
+ templateCache.clear();
1314
+ }
1315
+ function resolveInline(template) {
1316
+ return buildResolved(template, "inline");
1317
+ }
1318
+ function buildResolved(template, sourceType) {
1319
+ const validation = validateShellTemplate(template);
1320
+ if (!validation.valid) {
1321
+ throw new Error(
1322
+ `Custom shell template is missing required placeholder(s): ${validation.missingRequired.map((n) => `{{${n}}}`).join(", ")}`
1323
+ );
1324
+ }
1325
+ return { template, sourceType, validation };
1326
+ }
1327
+ async function fetchWithTimeout(url, timeoutMs) {
1328
+ const controller = new AbortController();
1329
+ const timer = setTimeout(() => controller.abort(), timeoutMs);
1330
+ try {
1331
+ const response = await fetch(url, { signal: controller.signal });
1332
+ if (!response.ok) {
1333
+ throw new Error(`Failed to fetch shell template from ${url}: HTTP ${response.status}`);
1334
+ }
1335
+ return await response.text();
1336
+ } catch (err) {
1337
+ if (err instanceof Error && err.name === "AbortError") {
1338
+ throw new Error(`Shell template fetch timed out after ${timeoutMs}ms: ${url}`);
1339
+ }
1340
+ throw err;
1341
+ } finally {
1342
+ clearTimeout(timer);
1343
+ }
1344
+ }
1345
+ async function resolveNpmTemplate(source, specifier, resolver) {
1346
+ const cdnUrl = resolveNpmToCdnUrl(specifier, resolver);
1347
+ const moduleText = await fetchWithTimeout(cdnUrl, DEFAULT_TIMEOUT_MS);
1348
+ return extractTemplateFromModule(moduleText, source.exportName);
1349
+ }
1350
+ function resolveNpmToCdnUrl(specifier, resolver) {
1351
+ if (resolver) {
1352
+ const resolved = resolver.resolve(specifier);
1353
+ if (resolved) {
1354
+ return resolved.value;
1355
+ }
1356
+ }
1357
+ return `https://esm.sh/${specifier}`;
1358
+ }
1359
+ function extractTemplateFromModule(moduleText, exportName) {
1360
+ if (exportName && exportName !== "default") {
1361
+ const namedPattern = new RegExp(
1362
+ `export\\s+(?:const|let|var)\\s+${escapeRegExp(exportName)}\\s*=\\s*\`([\\s\\S]*?)\``
1363
+ );
1364
+ const namedMatch = moduleText.match(namedPattern);
1365
+ if (namedMatch) {
1366
+ return namedMatch[1];
1367
+ }
1368
+ throw new Error(
1369
+ `Could not extract named export "${exportName}" from npm shell package. The package must export a template literal string.`
1370
+ );
1371
+ }
1372
+ const defaultPattern = /export\s+default\s+`([\s\S]*?)`/;
1373
+ const defaultMatch = moduleText.match(defaultPattern);
1374
+ if (defaultMatch) {
1375
+ return defaultMatch[1];
1376
+ }
1377
+ throw new Error(
1378
+ "Could not extract default export from npm shell package. The package must export a template literal string (e.g., export default `<html>...</html>`)."
1379
+ );
1380
+ }
1381
+ function escapeRegExp(str) {
1382
+ return str.replace(/[.*+?^${}()|[\]\\]/g, "\\$&");
1383
+ }
1384
+ export {
1385
+ DEFAULT_CDN_DOMAINS,
1386
+ DEFAULT_CSP_DIRECTIVES,
1387
+ OPTIONAL_PLACEHOLDERS,
1388
+ REQUIRED_PLACEHOLDERS,
1389
+ RESTRICTIVE_CSP_DIRECTIVES,
1390
+ SHELL_PLACEHOLDERS,
1391
+ SHELL_PLACEHOLDER_NAMES,
1392
+ applyShellTemplate,
1393
+ buildCSPDirectives,
1394
+ buildCSPMetaTag,
1395
+ buildDataInjectionScript,
1396
+ buildShell,
1397
+ clearShellTemplateCache,
1398
+ createTemplateHelpers,
1399
+ isInlineShellSource,
1400
+ isNpmShellSource,
1401
+ isUrlShellSource,
1402
+ resolveShellTemplate,
1403
+ sanitizeCSPDomains,
1404
+ validateCSPDomain,
1405
+ validateShellTemplate
1406
+ };