@frontmcp/uipack 0.6.1

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 (417) hide show
  1. package/CLAUDE.md +246 -0
  2. package/LICENSE +201 -0
  3. package/README.md +150 -0
  4. package/adapters/index.d.ts +13 -0
  5. package/adapters/index.d.ts.map +1 -0
  6. package/adapters/index.js +462 -0
  7. package/adapters/platform-meta.d.ts +166 -0
  8. package/adapters/platform-meta.d.ts.map +1 -0
  9. package/adapters/response-builder.d.ts +108 -0
  10. package/adapters/response-builder.d.ts.map +1 -0
  11. package/adapters/serving-mode.d.ts +107 -0
  12. package/adapters/serving-mode.d.ts.map +1 -0
  13. package/base-template/bridge.d.ts +90 -0
  14. package/base-template/bridge.d.ts.map +1 -0
  15. package/base-template/default-base-template.d.ts +92 -0
  16. package/base-template/default-base-template.d.ts.map +1 -0
  17. package/base-template/index.d.ts +15 -0
  18. package/base-template/index.d.ts.map +1 -0
  19. package/base-template/index.js +1398 -0
  20. package/base-template/polyfills.d.ts +31 -0
  21. package/base-template/polyfills.d.ts.map +1 -0
  22. package/base-template/theme-styles.d.ts +74 -0
  23. package/base-template/theme-styles.d.ts.map +1 -0
  24. package/bridge-runtime/iife-generator.d.ts +62 -0
  25. package/bridge-runtime/iife-generator.d.ts.map +1 -0
  26. package/bridge-runtime/index.d.ts +10 -0
  27. package/bridge-runtime/index.d.ts.map +1 -0
  28. package/bridge-runtime/index.js +883 -0
  29. package/build/cdn-resources.d.ts +243 -0
  30. package/build/cdn-resources.d.ts.map +1 -0
  31. package/build/index.d.ts +295 -0
  32. package/build/index.d.ts.map +1 -0
  33. package/build/index.js +6861 -0
  34. package/build/widget-manifest.d.ts +362 -0
  35. package/build/widget-manifest.d.ts.map +1 -0
  36. package/bundler/cache.d.ts +173 -0
  37. package/bundler/cache.d.ts.map +1 -0
  38. package/bundler/file-cache/component-builder.d.ts +167 -0
  39. package/bundler/file-cache/component-builder.d.ts.map +1 -0
  40. package/bundler/file-cache/hash-calculator.d.ts +155 -0
  41. package/bundler/file-cache/hash-calculator.d.ts.map +1 -0
  42. package/bundler/file-cache/index.d.ts +12 -0
  43. package/bundler/file-cache/index.d.ts.map +1 -0
  44. package/bundler/file-cache/storage/filesystem.d.ts +149 -0
  45. package/bundler/file-cache/storage/filesystem.d.ts.map +1 -0
  46. package/bundler/file-cache/storage/index.d.ts +11 -0
  47. package/bundler/file-cache/storage/index.d.ts.map +1 -0
  48. package/bundler/file-cache/storage/interface.d.ts +152 -0
  49. package/bundler/file-cache/storage/interface.d.ts.map +1 -0
  50. package/bundler/file-cache/storage/redis.d.ts +139 -0
  51. package/bundler/file-cache/storage/redis.d.ts.map +1 -0
  52. package/bundler/index.d.ts +35 -0
  53. package/bundler/index.d.ts.map +1 -0
  54. package/bundler/index.js +2947 -0
  55. package/bundler/sandbox/enclave-adapter.d.ts +121 -0
  56. package/bundler/sandbox/enclave-adapter.d.ts.map +1 -0
  57. package/bundler/sandbox/executor.d.ts +14 -0
  58. package/bundler/sandbox/executor.d.ts.map +1 -0
  59. package/bundler/sandbox/policy.d.ts +62 -0
  60. package/bundler/sandbox/policy.d.ts.map +1 -0
  61. package/bundler/types.d.ts +702 -0
  62. package/bundler/types.d.ts.map +1 -0
  63. package/dependency/cdn-registry.d.ts +98 -0
  64. package/dependency/cdn-registry.d.ts.map +1 -0
  65. package/dependency/import-map.d.ts +186 -0
  66. package/dependency/import-map.d.ts.map +1 -0
  67. package/dependency/import-parser.d.ts +82 -0
  68. package/dependency/import-parser.d.ts.map +1 -0
  69. package/dependency/index.d.ts +17 -0
  70. package/dependency/index.d.ts.map +1 -0
  71. package/dependency/index.js +3215 -0
  72. package/dependency/resolver.d.ts +164 -0
  73. package/dependency/resolver.d.ts.map +1 -0
  74. package/dependency/schemas.d.ts +486 -0
  75. package/dependency/schemas.d.ts.map +1 -0
  76. package/dependency/template-loader.d.ts +204 -0
  77. package/dependency/template-loader.d.ts.map +1 -0
  78. package/dependency/template-processor.d.ts +118 -0
  79. package/dependency/template-processor.d.ts.map +1 -0
  80. package/dependency/types.d.ts +739 -0
  81. package/dependency/types.d.ts.map +1 -0
  82. package/esm/adapters/index.d.ts +13 -0
  83. package/esm/adapters/index.d.ts.map +1 -0
  84. package/esm/adapters/index.js +427 -0
  85. package/esm/adapters/platform-meta.d.ts +166 -0
  86. package/esm/adapters/platform-meta.d.ts.map +1 -0
  87. package/esm/adapters/response-builder.d.ts +108 -0
  88. package/esm/adapters/response-builder.d.ts.map +1 -0
  89. package/esm/adapters/serving-mode.d.ts +107 -0
  90. package/esm/adapters/serving-mode.d.ts.map +1 -0
  91. package/esm/base-template/bridge.d.ts +90 -0
  92. package/esm/base-template/bridge.d.ts.map +1 -0
  93. package/esm/base-template/default-base-template.d.ts +92 -0
  94. package/esm/base-template/default-base-template.d.ts.map +1 -0
  95. package/esm/base-template/index.d.ts +15 -0
  96. package/esm/base-template/index.d.ts.map +1 -0
  97. package/esm/base-template/index.js +1364 -0
  98. package/esm/base-template/polyfills.d.ts +31 -0
  99. package/esm/base-template/polyfills.d.ts.map +1 -0
  100. package/esm/base-template/theme-styles.d.ts +74 -0
  101. package/esm/base-template/theme-styles.d.ts.map +1 -0
  102. package/esm/bridge-runtime/iife-generator.d.ts +62 -0
  103. package/esm/bridge-runtime/iife-generator.d.ts.map +1 -0
  104. package/esm/bridge-runtime/index.d.ts +10 -0
  105. package/esm/bridge-runtime/index.d.ts.map +1 -0
  106. package/esm/bridge-runtime/index.js +853 -0
  107. package/esm/build/cdn-resources.d.ts +243 -0
  108. package/esm/build/cdn-resources.d.ts.map +1 -0
  109. package/esm/build/index.d.ts +295 -0
  110. package/esm/build/index.d.ts.map +1 -0
  111. package/esm/build/index.js +6786 -0
  112. package/esm/build/widget-manifest.d.ts +362 -0
  113. package/esm/build/widget-manifest.d.ts.map +1 -0
  114. package/esm/bundler/cache.d.ts +173 -0
  115. package/esm/bundler/cache.d.ts.map +1 -0
  116. package/esm/bundler/file-cache/component-builder.d.ts +167 -0
  117. package/esm/bundler/file-cache/component-builder.d.ts.map +1 -0
  118. package/esm/bundler/file-cache/hash-calculator.d.ts +155 -0
  119. package/esm/bundler/file-cache/hash-calculator.d.ts.map +1 -0
  120. package/esm/bundler/file-cache/index.d.ts +12 -0
  121. package/esm/bundler/file-cache/index.d.ts.map +1 -0
  122. package/esm/bundler/file-cache/storage/filesystem.d.ts +149 -0
  123. package/esm/bundler/file-cache/storage/filesystem.d.ts.map +1 -0
  124. package/esm/bundler/file-cache/storage/index.d.ts +11 -0
  125. package/esm/bundler/file-cache/storage/index.d.ts.map +1 -0
  126. package/esm/bundler/file-cache/storage/interface.d.ts +152 -0
  127. package/esm/bundler/file-cache/storage/interface.d.ts.map +1 -0
  128. package/esm/bundler/file-cache/storage/redis.d.ts +139 -0
  129. package/esm/bundler/file-cache/storage/redis.d.ts.map +1 -0
  130. package/esm/bundler/index.d.ts +35 -0
  131. package/esm/bundler/index.d.ts.map +1 -0
  132. package/esm/bundler/index.js +2882 -0
  133. package/esm/bundler/sandbox/enclave-adapter.d.ts +121 -0
  134. package/esm/bundler/sandbox/enclave-adapter.d.ts.map +1 -0
  135. package/esm/bundler/sandbox/executor.d.ts +14 -0
  136. package/esm/bundler/sandbox/executor.d.ts.map +1 -0
  137. package/esm/bundler/sandbox/policy.d.ts +62 -0
  138. package/esm/bundler/sandbox/policy.d.ts.map +1 -0
  139. package/esm/bundler/types.d.ts +702 -0
  140. package/esm/bundler/types.d.ts.map +1 -0
  141. package/esm/dependency/cdn-registry.d.ts +98 -0
  142. package/esm/dependency/cdn-registry.d.ts.map +1 -0
  143. package/esm/dependency/import-map.d.ts +186 -0
  144. package/esm/dependency/import-map.d.ts.map +1 -0
  145. package/esm/dependency/import-parser.d.ts +82 -0
  146. package/esm/dependency/import-parser.d.ts.map +1 -0
  147. package/esm/dependency/index.d.ts +17 -0
  148. package/esm/dependency/index.d.ts.map +1 -0
  149. package/esm/dependency/index.js +3096 -0
  150. package/esm/dependency/resolver.d.ts +164 -0
  151. package/esm/dependency/resolver.d.ts.map +1 -0
  152. package/esm/dependency/schemas.d.ts +486 -0
  153. package/esm/dependency/schemas.d.ts.map +1 -0
  154. package/esm/dependency/template-loader.d.ts +204 -0
  155. package/esm/dependency/template-loader.d.ts.map +1 -0
  156. package/esm/dependency/template-processor.d.ts +118 -0
  157. package/esm/dependency/template-processor.d.ts.map +1 -0
  158. package/esm/dependency/types.d.ts +739 -0
  159. package/esm/dependency/types.d.ts.map +1 -0
  160. package/esm/handlebars/expression-extractor.d.ts +147 -0
  161. package/esm/handlebars/expression-extractor.d.ts.map +1 -0
  162. package/esm/handlebars/helpers.d.ts +339 -0
  163. package/esm/handlebars/helpers.d.ts.map +1 -0
  164. package/esm/handlebars/index.d.ts +195 -0
  165. package/esm/handlebars/index.d.ts.map +1 -0
  166. package/esm/handlebars/index.js +587 -0
  167. package/esm/index.d.ts +50 -0
  168. package/esm/index.d.ts.map +1 -0
  169. package/esm/index.js +12434 -0
  170. package/esm/package.json +68 -0
  171. package/esm/registry/index.d.ts +46 -0
  172. package/esm/registry/index.d.ts.map +1 -0
  173. package/esm/registry/index.js +6237 -0
  174. package/esm/registry/render-template.d.ts +91 -0
  175. package/esm/registry/render-template.d.ts.map +1 -0
  176. package/esm/registry/tool-ui.registry.d.ts +294 -0
  177. package/esm/registry/tool-ui.registry.d.ts.map +1 -0
  178. package/esm/registry/uri-utils.d.ts +56 -0
  179. package/esm/registry/uri-utils.d.ts.map +1 -0
  180. package/esm/renderers/cache.d.ts +145 -0
  181. package/esm/renderers/cache.d.ts.map +1 -0
  182. package/esm/renderers/html.renderer.d.ts +123 -0
  183. package/esm/renderers/html.renderer.d.ts.map +1 -0
  184. package/esm/renderers/index.d.ts +36 -0
  185. package/esm/renderers/index.d.ts.map +1 -0
  186. package/esm/renderers/index.js +1654 -0
  187. package/esm/renderers/mdx.renderer.d.ts +120 -0
  188. package/esm/renderers/mdx.renderer.d.ts.map +1 -0
  189. package/esm/renderers/registry.d.ts +133 -0
  190. package/esm/renderers/registry.d.ts.map +1 -0
  191. package/esm/renderers/types.d.ts +342 -0
  192. package/esm/renderers/types.d.ts.map +1 -0
  193. package/esm/renderers/utils/detect.d.ts +107 -0
  194. package/esm/renderers/utils/detect.d.ts.map +1 -0
  195. package/esm/renderers/utils/hash.d.ts +40 -0
  196. package/esm/renderers/utils/hash.d.ts.map +1 -0
  197. package/esm/renderers/utils/index.d.ts +9 -0
  198. package/esm/renderers/utils/index.d.ts.map +1 -0
  199. package/esm/renderers/utils/transpiler.d.ts +89 -0
  200. package/esm/renderers/utils/transpiler.d.ts.map +1 -0
  201. package/esm/runtime/adapters/html.adapter.d.ts +59 -0
  202. package/esm/runtime/adapters/html.adapter.d.ts.map +1 -0
  203. package/esm/runtime/adapters/index.d.ts +26 -0
  204. package/esm/runtime/adapters/index.d.ts.map +1 -0
  205. package/esm/runtime/adapters/mdx.adapter.d.ts +73 -0
  206. package/esm/runtime/adapters/mdx.adapter.d.ts.map +1 -0
  207. package/esm/runtime/adapters/types.d.ts +95 -0
  208. package/esm/runtime/adapters/types.d.ts.map +1 -0
  209. package/esm/runtime/csp.d.ts +48 -0
  210. package/esm/runtime/csp.d.ts.map +1 -0
  211. package/esm/runtime/index.d.ts +17 -0
  212. package/esm/runtime/index.d.ts.map +1 -0
  213. package/esm/runtime/index.js +4976 -0
  214. package/esm/runtime/mcp-bridge.d.ts +101 -0
  215. package/esm/runtime/mcp-bridge.d.ts.map +1 -0
  216. package/esm/runtime/renderer-runtime.d.ts +133 -0
  217. package/esm/runtime/renderer-runtime.d.ts.map +1 -0
  218. package/esm/runtime/sanitizer.d.ts +172 -0
  219. package/esm/runtime/sanitizer.d.ts.map +1 -0
  220. package/esm/runtime/types.d.ts +415 -0
  221. package/esm/runtime/types.d.ts.map +1 -0
  222. package/esm/runtime/wrapper.d.ts +421 -0
  223. package/esm/runtime/wrapper.d.ts.map +1 -0
  224. package/esm/styles/index.d.ts +8 -0
  225. package/esm/styles/index.d.ts.map +1 -0
  226. package/esm/styles/index.js +171 -0
  227. package/esm/styles/variants.d.ts +51 -0
  228. package/esm/styles/variants.d.ts.map +1 -0
  229. package/esm/theme/cdn.d.ts +195 -0
  230. package/esm/theme/cdn.d.ts.map +1 -0
  231. package/esm/theme/index.d.ts +18 -0
  232. package/esm/theme/index.d.ts.map +1 -0
  233. package/esm/theme/index.js +700 -0
  234. package/esm/theme/platforms.d.ts +107 -0
  235. package/esm/theme/platforms.d.ts.map +1 -0
  236. package/esm/theme/presets/github-openai.d.ts +50 -0
  237. package/esm/theme/presets/github-openai.d.ts.map +1 -0
  238. package/esm/theme/presets/index.d.ts +11 -0
  239. package/esm/theme/presets/index.d.ts.map +1 -0
  240. package/esm/theme/theme.d.ts +396 -0
  241. package/esm/theme/theme.d.ts.map +1 -0
  242. package/esm/tool-template/builder.d.ts +213 -0
  243. package/esm/tool-template/builder.d.ts.map +1 -0
  244. package/esm/tool-template/index.d.ts +16 -0
  245. package/esm/tool-template/index.d.ts.map +1 -0
  246. package/esm/tool-template/index.js +3518 -0
  247. package/esm/types/index.d.ts +14 -0
  248. package/esm/types/index.d.ts.map +1 -0
  249. package/esm/types/index.js +75 -0
  250. package/esm/types/ui-config.d.ts +641 -0
  251. package/esm/types/ui-config.d.ts.map +1 -0
  252. package/esm/types/ui-runtime.d.ts +1008 -0
  253. package/esm/types/ui-runtime.d.ts.map +1 -0
  254. package/esm/typings/cache/cache-adapter.d.ts +125 -0
  255. package/esm/typings/cache/cache-adapter.d.ts.map +1 -0
  256. package/esm/typings/cache/index.d.ts +10 -0
  257. package/esm/typings/cache/index.d.ts.map +1 -0
  258. package/esm/typings/cache/memory-cache.d.ts +92 -0
  259. package/esm/typings/cache/memory-cache.d.ts.map +1 -0
  260. package/esm/typings/dts-parser.d.ts +90 -0
  261. package/esm/typings/dts-parser.d.ts.map +1 -0
  262. package/esm/typings/index.d.ts +48 -0
  263. package/esm/typings/index.d.ts.map +1 -0
  264. package/esm/typings/index.js +812 -0
  265. package/esm/typings/schemas.d.ts +232 -0
  266. package/esm/typings/schemas.d.ts.map +1 -0
  267. package/esm/typings/type-fetcher.d.ts +89 -0
  268. package/esm/typings/type-fetcher.d.ts.map +1 -0
  269. package/esm/typings/types.d.ts +320 -0
  270. package/esm/typings/types.d.ts.map +1 -0
  271. package/esm/utils/escape-html.d.ts +58 -0
  272. package/esm/utils/escape-html.d.ts.map +1 -0
  273. package/esm/utils/index.d.ts +10 -0
  274. package/esm/utils/index.d.ts.map +1 -0
  275. package/esm/utils/index.js +40 -0
  276. package/esm/utils/safe-stringify.d.ts +30 -0
  277. package/esm/utils/safe-stringify.d.ts.map +1 -0
  278. package/esm/validation/error-box.d.ts +56 -0
  279. package/esm/validation/error-box.d.ts.map +1 -0
  280. package/esm/validation/index.d.ts +13 -0
  281. package/esm/validation/index.d.ts.map +1 -0
  282. package/esm/validation/index.js +542 -0
  283. package/esm/validation/schema-paths.d.ts +118 -0
  284. package/esm/validation/schema-paths.d.ts.map +1 -0
  285. package/esm/validation/template-validator.d.ts +143 -0
  286. package/esm/validation/template-validator.d.ts.map +1 -0
  287. package/esm/validation/wrapper.d.ts +97 -0
  288. package/esm/validation/wrapper.d.ts.map +1 -0
  289. package/handlebars/expression-extractor.d.ts +147 -0
  290. package/handlebars/expression-extractor.d.ts.map +1 -0
  291. package/handlebars/helpers.d.ts +339 -0
  292. package/handlebars/helpers.d.ts.map +1 -0
  293. package/handlebars/index.d.ts +195 -0
  294. package/handlebars/index.d.ts.map +1 -0
  295. package/handlebars/index.js +666 -0
  296. package/index.d.ts +50 -0
  297. package/index.d.ts.map +1 -0
  298. package/index.js +12683 -0
  299. package/package.json +66 -0
  300. package/registry/index.d.ts +46 -0
  301. package/registry/index.d.ts.map +1 -0
  302. package/registry/index.js +6280 -0
  303. package/registry/render-template.d.ts +91 -0
  304. package/registry/render-template.d.ts.map +1 -0
  305. package/registry/tool-ui.registry.d.ts +294 -0
  306. package/registry/tool-ui.registry.d.ts.map +1 -0
  307. package/registry/uri-utils.d.ts +56 -0
  308. package/registry/uri-utils.d.ts.map +1 -0
  309. package/renderers/cache.d.ts +145 -0
  310. package/renderers/cache.d.ts.map +1 -0
  311. package/renderers/html.renderer.d.ts +123 -0
  312. package/renderers/html.renderer.d.ts.map +1 -0
  313. package/renderers/index.d.ts +36 -0
  314. package/renderers/index.d.ts.map +1 -0
  315. package/renderers/index.js +1706 -0
  316. package/renderers/mdx.renderer.d.ts +120 -0
  317. package/renderers/mdx.renderer.d.ts.map +1 -0
  318. package/renderers/registry.d.ts +133 -0
  319. package/renderers/registry.d.ts.map +1 -0
  320. package/renderers/types.d.ts +342 -0
  321. package/renderers/types.d.ts.map +1 -0
  322. package/renderers/utils/detect.d.ts +107 -0
  323. package/renderers/utils/detect.d.ts.map +1 -0
  324. package/renderers/utils/hash.d.ts +40 -0
  325. package/renderers/utils/hash.d.ts.map +1 -0
  326. package/renderers/utils/index.d.ts +9 -0
  327. package/renderers/utils/index.d.ts.map +1 -0
  328. package/renderers/utils/transpiler.d.ts +89 -0
  329. package/renderers/utils/transpiler.d.ts.map +1 -0
  330. package/runtime/adapters/html.adapter.d.ts +59 -0
  331. package/runtime/adapters/html.adapter.d.ts.map +1 -0
  332. package/runtime/adapters/index.d.ts +26 -0
  333. package/runtime/adapters/index.d.ts.map +1 -0
  334. package/runtime/adapters/mdx.adapter.d.ts +73 -0
  335. package/runtime/adapters/mdx.adapter.d.ts.map +1 -0
  336. package/runtime/adapters/types.d.ts +95 -0
  337. package/runtime/adapters/types.d.ts.map +1 -0
  338. package/runtime/csp.d.ts +48 -0
  339. package/runtime/csp.d.ts.map +1 -0
  340. package/runtime/index.d.ts +17 -0
  341. package/runtime/index.d.ts.map +1 -0
  342. package/runtime/index.js +5052 -0
  343. package/runtime/mcp-bridge.d.ts +101 -0
  344. package/runtime/mcp-bridge.d.ts.map +1 -0
  345. package/runtime/renderer-runtime.d.ts +133 -0
  346. package/runtime/renderer-runtime.d.ts.map +1 -0
  347. package/runtime/sanitizer.d.ts +172 -0
  348. package/runtime/sanitizer.d.ts.map +1 -0
  349. package/runtime/types.d.ts +415 -0
  350. package/runtime/types.d.ts.map +1 -0
  351. package/runtime/wrapper.d.ts +421 -0
  352. package/runtime/wrapper.d.ts.map +1 -0
  353. package/styles/index.d.ts +8 -0
  354. package/styles/index.d.ts.map +1 -0
  355. package/styles/index.js +222 -0
  356. package/styles/variants.d.ts +51 -0
  357. package/styles/variants.d.ts.map +1 -0
  358. package/theme/cdn.d.ts +195 -0
  359. package/theme/cdn.d.ts.map +1 -0
  360. package/theme/index.d.ts +18 -0
  361. package/theme/index.d.ts.map +1 -0
  362. package/theme/index.js +757 -0
  363. package/theme/platforms.d.ts +107 -0
  364. package/theme/platforms.d.ts.map +1 -0
  365. package/theme/presets/github-openai.d.ts +50 -0
  366. package/theme/presets/github-openai.d.ts.map +1 -0
  367. package/theme/presets/index.d.ts +11 -0
  368. package/theme/presets/index.d.ts.map +1 -0
  369. package/theme/theme.d.ts +396 -0
  370. package/theme/theme.d.ts.map +1 -0
  371. package/tool-template/builder.d.ts +213 -0
  372. package/tool-template/builder.d.ts.map +1 -0
  373. package/tool-template/index.d.ts +16 -0
  374. package/tool-template/index.d.ts.map +1 -0
  375. package/tool-template/index.js +3562 -0
  376. package/types/index.d.ts +14 -0
  377. package/types/index.d.ts.map +1 -0
  378. package/types/index.js +108 -0
  379. package/types/ui-config.d.ts +641 -0
  380. package/types/ui-config.d.ts.map +1 -0
  381. package/types/ui-runtime.d.ts +1008 -0
  382. package/types/ui-runtime.d.ts.map +1 -0
  383. package/typings/cache/cache-adapter.d.ts +125 -0
  384. package/typings/cache/cache-adapter.d.ts.map +1 -0
  385. package/typings/cache/index.d.ts +10 -0
  386. package/typings/cache/index.d.ts.map +1 -0
  387. package/typings/cache/memory-cache.d.ts +92 -0
  388. package/typings/cache/memory-cache.d.ts.map +1 -0
  389. package/typings/dts-parser.d.ts +90 -0
  390. package/typings/dts-parser.d.ts.map +1 -0
  391. package/typings/index.d.ts +48 -0
  392. package/typings/index.d.ts.map +1 -0
  393. package/typings/index.js +868 -0
  394. package/typings/schemas.d.ts +232 -0
  395. package/typings/schemas.d.ts.map +1 -0
  396. package/typings/type-fetcher.d.ts +89 -0
  397. package/typings/type-fetcher.d.ts.map +1 -0
  398. package/typings/types.d.ts +320 -0
  399. package/typings/types.d.ts.map +1 -0
  400. package/utils/escape-html.d.ts +58 -0
  401. package/utils/escape-html.d.ts.map +1 -0
  402. package/utils/index.d.ts +10 -0
  403. package/utils/index.d.ts.map +1 -0
  404. package/utils/index.js +70 -0
  405. package/utils/safe-stringify.d.ts +30 -0
  406. package/utils/safe-stringify.d.ts.map +1 -0
  407. package/validation/error-box.d.ts +56 -0
  408. package/validation/error-box.d.ts.map +1 -0
  409. package/validation/index.d.ts +13 -0
  410. package/validation/index.d.ts.map +1 -0
  411. package/validation/index.js +583 -0
  412. package/validation/schema-paths.d.ts +118 -0
  413. package/validation/schema-paths.d.ts.map +1 -0
  414. package/validation/template-validator.d.ts +143 -0
  415. package/validation/template-validator.d.ts.map +1 -0
  416. package/validation/wrapper.d.ts +97 -0
  417. package/validation/wrapper.d.ts.map +1 -0
@@ -0,0 +1,4976 @@
1
+ var __defProp = Object.defineProperty;
2
+ var __getOwnPropNames = Object.getOwnPropertyNames;
3
+ var __esm = (fn, res) => function __init() {
4
+ return fn && (res = (0, fn[__getOwnPropNames(fn)[0]])(fn = 0)), res;
5
+ };
6
+ var __export = (target, all) => {
7
+ for (var name in all)
8
+ __defProp(target, name, { get: all[name], enumerable: true });
9
+ };
10
+
11
+ // libs/uipack/src/utils/safe-stringify.ts
12
+ var init_safe_stringify = __esm({
13
+ "libs/uipack/src/utils/safe-stringify.ts"() {
14
+ "use strict";
15
+ }
16
+ });
17
+
18
+ // libs/uipack/src/utils/escape-html.ts
19
+ function escapeHtml(str) {
20
+ if (str === null || str === void 0) {
21
+ return "";
22
+ }
23
+ const s = String(str);
24
+ 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");
25
+ }
26
+ var init_escape_html = __esm({
27
+ "libs/uipack/src/utils/escape-html.ts"() {
28
+ "use strict";
29
+ }
30
+ });
31
+
32
+ // libs/uipack/src/utils/index.ts
33
+ var init_utils = __esm({
34
+ "libs/uipack/src/utils/index.ts"() {
35
+ "use strict";
36
+ init_safe_stringify();
37
+ init_escape_html();
38
+ }
39
+ });
40
+
41
+ // libs/uipack/src/handlebars/helpers.ts
42
+ function formatDate(date, format) {
43
+ if (date === null || date === void 0) {
44
+ return "";
45
+ }
46
+ let dateObj;
47
+ if (date instanceof Date) {
48
+ dateObj = date;
49
+ } else if (typeof date === "string" || typeof date === "number") {
50
+ dateObj = new Date(date);
51
+ } else {
52
+ return String(date);
53
+ }
54
+ if (isNaN(dateObj.getTime())) {
55
+ return String(date);
56
+ }
57
+ const options = {};
58
+ switch (format) {
59
+ case "short":
60
+ options.dateStyle = "short";
61
+ break;
62
+ case "medium":
63
+ options.dateStyle = "medium";
64
+ break;
65
+ case "long":
66
+ options.dateStyle = "long";
67
+ break;
68
+ case "full":
69
+ options.dateStyle = "full";
70
+ break;
71
+ case "time":
72
+ options.timeStyle = "short";
73
+ break;
74
+ case "datetime":
75
+ options.dateStyle = "medium";
76
+ options.timeStyle = "short";
77
+ break;
78
+ case "iso":
79
+ return dateObj.toISOString();
80
+ case "relative":
81
+ return formatRelativeDate(dateObj);
82
+ default:
83
+ options.dateStyle = "medium";
84
+ }
85
+ return new Intl.DateTimeFormat("en-US", options).format(dateObj);
86
+ }
87
+ function formatRelativeDate(date) {
88
+ const now = /* @__PURE__ */ new Date();
89
+ const diffMs = now.getTime() - date.getTime();
90
+ const diffSecs = Math.floor(diffMs / 1e3);
91
+ const diffMins = Math.floor(diffSecs / 60);
92
+ const diffHours = Math.floor(diffMins / 60);
93
+ const diffDays = Math.floor(diffHours / 24);
94
+ if (diffSecs < 60) {
95
+ return "just now";
96
+ } else if (diffMins < 60) {
97
+ return `${diffMins} minute${diffMins === 1 ? "" : "s"} ago`;
98
+ } else if (diffHours < 24) {
99
+ return `${diffHours} hour${diffHours === 1 ? "" : "s"} ago`;
100
+ } else if (diffDays < 7) {
101
+ return `${diffDays} day${diffDays === 1 ? "" : "s"} ago`;
102
+ } else {
103
+ return formatDate(date, "medium");
104
+ }
105
+ }
106
+ function formatCurrency(amount, currency) {
107
+ if (amount === null || amount === void 0) {
108
+ return "";
109
+ }
110
+ const num = typeof amount === "number" ? amount : parseFloat(String(amount));
111
+ if (isNaN(num)) {
112
+ return String(amount);
113
+ }
114
+ return new Intl.NumberFormat("en-US", {
115
+ style: "currency",
116
+ currency: typeof currency === "string" ? currency : "USD"
117
+ }).format(num);
118
+ }
119
+ function formatNumber(value, decimals) {
120
+ if (value === null || value === void 0) {
121
+ return "";
122
+ }
123
+ const num = typeof value === "number" ? value : parseFloat(String(value));
124
+ if (isNaN(num)) {
125
+ return String(value);
126
+ }
127
+ const options = {};
128
+ if (typeof decimals === "number") {
129
+ options.minimumFractionDigits = decimals;
130
+ options.maximumFractionDigits = decimals;
131
+ }
132
+ return new Intl.NumberFormat("en-US", options).format(num);
133
+ }
134
+ function jsonEmbed(data) {
135
+ const json2 = JSON.stringify(data ?? null);
136
+ return json2.replace(/</g, "\\u003c").replace(/>/g, "\\u003e").replace(/&/g, "\\u0026").replace(/\u2028/g, "\\u2028").replace(/\u2029/g, "\\u2029");
137
+ }
138
+ function json(data, pretty) {
139
+ return JSON.stringify(data ?? null, null, pretty ? 2 : void 0);
140
+ }
141
+ function eq(a, b) {
142
+ return a === b;
143
+ }
144
+ function ne(a, b) {
145
+ return a !== b;
146
+ }
147
+ function gt(a, b) {
148
+ return Number(a) > Number(b);
149
+ }
150
+ function gte(a, b) {
151
+ return Number(a) >= Number(b);
152
+ }
153
+ function lt(a, b) {
154
+ return Number(a) < Number(b);
155
+ }
156
+ function lte(a, b) {
157
+ return Number(a) <= Number(b);
158
+ }
159
+ function and(a, b) {
160
+ return Boolean(a) && Boolean(b);
161
+ }
162
+ function or(a, b) {
163
+ return Boolean(a) || Boolean(b);
164
+ }
165
+ function not(value) {
166
+ return !value;
167
+ }
168
+ function first(arr) {
169
+ if (!Array.isArray(arr)) return void 0;
170
+ return arr[0];
171
+ }
172
+ function last(arr) {
173
+ if (!Array.isArray(arr)) return void 0;
174
+ return arr[arr.length - 1];
175
+ }
176
+ function length(value) {
177
+ if (Array.isArray(value)) return value.length;
178
+ if (typeof value === "string") return value.length;
179
+ return 0;
180
+ }
181
+ function includes(arr, value) {
182
+ if (!Array.isArray(arr)) return false;
183
+ return arr.includes(value);
184
+ }
185
+ function join(arr, separator) {
186
+ if (!Array.isArray(arr)) return "";
187
+ return arr.join(typeof separator === "string" ? separator : ", ");
188
+ }
189
+ function uppercase(str) {
190
+ return String(str ?? "").toUpperCase();
191
+ }
192
+ function lowercase(str) {
193
+ return String(str ?? "").toLowerCase();
194
+ }
195
+ function capitalize(str) {
196
+ const s = String(str ?? "");
197
+ return s.charAt(0).toUpperCase() + s.slice(1).toLowerCase();
198
+ }
199
+ function truncate(str, maxLength, suffix) {
200
+ const s = String(str ?? "");
201
+ const len = typeof maxLength === "number" ? maxLength : 50;
202
+ const suf = typeof suffix === "string" ? suffix : "...";
203
+ if (s.length <= len) return s;
204
+ return s.slice(0, len - suf.length) + suf;
205
+ }
206
+ function defaultValue(value, defaultValue2) {
207
+ return value || defaultValue2;
208
+ }
209
+ function uniqueId(prefix) {
210
+ const id = ++idCounter;
211
+ return prefix ? `${prefix}-${id}` : `id-${id}`;
212
+ }
213
+ function resetUniqueIdCounter() {
214
+ idCounter = 0;
215
+ }
216
+ function classNames(...classes) {
217
+ return classes.filter(Boolean).map(String).join(" ");
218
+ }
219
+ var idCounter, builtinHelpers;
220
+ var init_helpers = __esm({
221
+ "libs/uipack/src/handlebars/helpers.ts"() {
222
+ "use strict";
223
+ init_utils();
224
+ idCounter = 0;
225
+ builtinHelpers = {
226
+ // Escaping
227
+ escapeHtml,
228
+ // Formatting
229
+ formatDate,
230
+ formatCurrency,
231
+ formatNumber,
232
+ json,
233
+ jsonEmbed,
234
+ // Comparison
235
+ eq,
236
+ ne,
237
+ gt,
238
+ gte,
239
+ lt,
240
+ lte,
241
+ // Logical
242
+ and,
243
+ or,
244
+ not,
245
+ // Array
246
+ first,
247
+ last,
248
+ length,
249
+ includes,
250
+ join,
251
+ // String
252
+ uppercase,
253
+ lowercase,
254
+ capitalize,
255
+ truncate,
256
+ // Utility
257
+ default: defaultValue,
258
+ uniqueId,
259
+ classNames
260
+ };
261
+ }
262
+ });
263
+
264
+ // libs/uipack/src/handlebars/expression-extractor.ts
265
+ function extractExpressions(template) {
266
+ const expressions = [];
267
+ const lines = template.split("\n");
268
+ const positionMap = buildPositionMap(template);
269
+ let match;
270
+ EXPRESSION_REGEX.lastIndex = 0;
271
+ while ((match = EXPRESSION_REGEX.exec(template)) !== null) {
272
+ const fullExpression = match[0];
273
+ const prefix = match[1];
274
+ const content = match[2].trim();
275
+ const position = positionMap.get(match.index) ?? { line: 1, column: 1 };
276
+ let type = "variable";
277
+ let helperName;
278
+ if (prefix === "/") {
279
+ type = "block-close";
280
+ helperName = content;
281
+ } else if (prefix === "#") {
282
+ type = "block";
283
+ const parts = content.split(/\s+/);
284
+ helperName = parts[0];
285
+ } else {
286
+ const parts = content.split(/\s+/);
287
+ if (parts.length > 1 && !content.startsWith("(")) {
288
+ const firstToken = parts[0];
289
+ if (!firstToken.includes(".") && !KEYWORDS.has(firstToken)) {
290
+ type = "helper";
291
+ helperName = firstToken;
292
+ }
293
+ }
294
+ }
295
+ const paths = extractPathsFromContent(content);
296
+ for (const path of paths) {
297
+ expressions.push({
298
+ path,
299
+ fullExpression,
300
+ line: position.line,
301
+ column: position.column,
302
+ type,
303
+ helperName
304
+ });
305
+ }
306
+ if (paths.length === 0 && type === "variable") {
307
+ const cleanContent = content.trim();
308
+ if (!KEYWORDS.has(cleanContent) && !cleanContent.includes(" ") && !cleanContent.startsWith("(")) {
309
+ }
310
+ }
311
+ }
312
+ return expressions;
313
+ }
314
+ function extractPathsFromContent(content) {
315
+ const paths = [];
316
+ let match;
317
+ const regex = new RegExp(PATH_REGEX.source, "g");
318
+ while ((match = regex.exec(content)) !== null) {
319
+ paths.push(match[0]);
320
+ }
321
+ return paths;
322
+ }
323
+ function buildPositionMap(template) {
324
+ const map = /* @__PURE__ */ new Map();
325
+ let line = 1;
326
+ let column = 1;
327
+ for (let i = 0; i < template.length; i++) {
328
+ map.set(i, { line, column });
329
+ if (template[i] === "\n") {
330
+ line++;
331
+ column = 1;
332
+ } else {
333
+ column++;
334
+ }
335
+ }
336
+ return map;
337
+ }
338
+ function extractVariablePaths(template) {
339
+ const expressions = extractExpressions(template);
340
+ const paths = new Set(expressions.map((e) => e.path));
341
+ return Array.from(paths);
342
+ }
343
+ function extractOutputPaths(template) {
344
+ return extractVariablePaths(template).filter((p) => p.startsWith("output."));
345
+ }
346
+ function extractInputPaths(template) {
347
+ return extractVariablePaths(template).filter((p) => p.startsWith("input."));
348
+ }
349
+ function extractStructuredContentPaths(template) {
350
+ return extractVariablePaths(template).filter((p) => p.startsWith("structuredContent."));
351
+ }
352
+ function extractAll(template) {
353
+ const expressions = extractExpressions(template);
354
+ const paths = [...new Set(expressions.map((e) => e.path))];
355
+ return {
356
+ expressions,
357
+ paths,
358
+ outputPaths: paths.filter((p) => p.startsWith("output.")),
359
+ inputPaths: paths.filter((p) => p.startsWith("input.")),
360
+ structuredContentPaths: paths.filter((p) => p.startsWith("structuredContent."))
361
+ };
362
+ }
363
+ function hasVariablePaths(template) {
364
+ return extractVariablePaths(template).length > 0;
365
+ }
366
+ function getExpressionAt(template, line, column) {
367
+ const expressions = extractExpressions(template);
368
+ return expressions.find((expr) => {
369
+ if (expr.line !== line) return false;
370
+ const exprEnd = expr.column + expr.fullExpression.length;
371
+ return column >= expr.column && column <= exprEnd;
372
+ });
373
+ }
374
+ function normalizePath(path) {
375
+ return path.replace(/\.\d+\./g, ".[].").replace(/\.\d+$/g, ".[]").replace(/\[\d+\]/g, ".[]");
376
+ }
377
+ var EXPRESSION_REGEX, PATH_REGEX, KEYWORDS;
378
+ var init_expression_extractor = __esm({
379
+ "libs/uipack/src/handlebars/expression-extractor.ts"() {
380
+ "use strict";
381
+ EXPRESSION_REGEX = /\{\{\{?(?!!)(#|\/)?([^}]+?)\}?\}\}/g;
382
+ PATH_REGEX = /\b(output|input|structuredContent)(\.[a-zA-Z_$][a-zA-Z0-9_$]*|\.\[[^\]]+\])+/g;
383
+ KEYWORDS = /* @__PURE__ */ new Set(["this", "else", "@index", "@key", "@first", "@last", "@root"]);
384
+ }
385
+ });
386
+
387
+ // libs/uipack/src/handlebars/index.ts
388
+ var handlebars_exports = {};
389
+ __export(handlebars_exports, {
390
+ HandlebarsRenderer: () => HandlebarsRenderer,
391
+ and: () => and,
392
+ builtinHelpers: () => builtinHelpers,
393
+ capitalize: () => capitalize,
394
+ classNames: () => classNames,
395
+ containsHandlebars: () => containsHandlebars,
396
+ createHandlebarsRenderer: () => createHandlebarsRenderer,
397
+ defaultValue: () => defaultValue,
398
+ eq: () => eq,
399
+ escapeHtml: () => escapeHtml,
400
+ extractAll: () => extractAll,
401
+ extractExpressions: () => extractExpressions,
402
+ extractInputPaths: () => extractInputPaths,
403
+ extractOutputPaths: () => extractOutputPaths,
404
+ extractStructuredContentPaths: () => extractStructuredContentPaths,
405
+ extractVariablePaths: () => extractVariablePaths,
406
+ first: () => first,
407
+ formatCurrency: () => formatCurrency,
408
+ formatDate: () => formatDate,
409
+ formatNumber: () => formatNumber,
410
+ getExpressionAt: () => getExpressionAt,
411
+ gt: () => gt,
412
+ gte: () => gte,
413
+ hasVariablePaths: () => hasVariablePaths,
414
+ includes: () => includes,
415
+ isHandlebarsAvailable: () => isHandlebarsAvailable,
416
+ join: () => join,
417
+ json: () => json,
418
+ jsonEmbed: () => jsonEmbed,
419
+ last: () => last,
420
+ length: () => length,
421
+ lowercase: () => lowercase,
422
+ lt: () => lt,
423
+ lte: () => lte,
424
+ ne: () => ne,
425
+ normalizePath: () => normalizePath,
426
+ not: () => not,
427
+ or: () => or,
428
+ renderTemplate: () => renderTemplate,
429
+ resetUniqueIdCounter: () => resetUniqueIdCounter,
430
+ truncate: () => truncate,
431
+ uniqueId: () => uniqueId,
432
+ uppercase: () => uppercase
433
+ });
434
+ async function loadHandlebars() {
435
+ if (Handlebars !== null) {
436
+ return Handlebars;
437
+ }
438
+ try {
439
+ Handlebars = await import("handlebars");
440
+ return Handlebars;
441
+ } catch {
442
+ throw new Error("Handlebars is required for template rendering. Install it: npm install handlebars");
443
+ }
444
+ }
445
+ async function isHandlebarsAvailable() {
446
+ try {
447
+ await loadHandlebars();
448
+ return true;
449
+ } catch {
450
+ return false;
451
+ }
452
+ }
453
+ function createHandlebarsRenderer(options) {
454
+ return new HandlebarsRenderer(options);
455
+ }
456
+ async function renderTemplate(template, context) {
457
+ const renderer = createHandlebarsRenderer();
458
+ return renderer.render(template, context);
459
+ }
460
+ function containsHandlebars(template) {
461
+ return HandlebarsRenderer.containsHandlebars(template);
462
+ }
463
+ var Handlebars, HandlebarsRenderer;
464
+ var init_handlebars = __esm({
465
+ "libs/uipack/src/handlebars/index.ts"() {
466
+ "use strict";
467
+ init_helpers();
468
+ init_helpers();
469
+ init_expression_extractor();
470
+ Handlebars = null;
471
+ HandlebarsRenderer = class {
472
+ options;
473
+ compiledTemplates = /* @__PURE__ */ new Map();
474
+ initialized = false;
475
+ hbs = null;
476
+ constructor(options = {}) {
477
+ this.options = {
478
+ strict: false,
479
+ autoEscape: true,
480
+ ...options
481
+ };
482
+ }
483
+ /**
484
+ * Initialize the renderer with Handlebars.
485
+ */
486
+ async init() {
487
+ if (this.initialized) return;
488
+ this.hbs = await loadHandlebars();
489
+ for (const [name, helper] of Object.entries(builtinHelpers)) {
490
+ this.hbs.registerHelper(name, helper);
491
+ }
492
+ if (this.options.helpers) {
493
+ for (const [name, helper] of Object.entries(this.options.helpers)) {
494
+ this.hbs.registerHelper(name, helper);
495
+ }
496
+ }
497
+ if (this.options.partials) {
498
+ for (const [name, template] of Object.entries(this.options.partials)) {
499
+ this.hbs.registerPartial(name, template);
500
+ }
501
+ }
502
+ this.initialized = true;
503
+ }
504
+ /**
505
+ * Render a Handlebars template.
506
+ *
507
+ * @param template - Template string
508
+ * @param context - Render context with input/output
509
+ * @returns Rendered HTML string
510
+ */
511
+ async render(template, context) {
512
+ await this.init();
513
+ if (!this.hbs) {
514
+ throw new Error("Handlebars not initialized");
515
+ }
516
+ let compiled = this.compiledTemplates.get(template);
517
+ if (!compiled) {
518
+ compiled = this.hbs.compile(template, {
519
+ strict: this.options.strict,
520
+ noEscape: !this.options.autoEscape
521
+ });
522
+ this.compiledTemplates.set(template, compiled);
523
+ }
524
+ const data = {
525
+ input: context.input ?? {},
526
+ output: context.output ?? {},
527
+ structuredContent: context.structuredContent,
528
+ // Also expose at root level for convenience
529
+ ...context.input,
530
+ ...typeof context.output === "object" && context.output !== null ? context.output : {}
531
+ };
532
+ try {
533
+ return compiled(data);
534
+ } catch (error) {
535
+ throw new Error(`Template rendering failed: ${error instanceof Error ? error.message : String(error)}`);
536
+ }
537
+ }
538
+ /**
539
+ * Render a template synchronously.
540
+ *
541
+ * Note: Requires Handlebars to be pre-loaded. Use `render()` for async loading.
542
+ *
543
+ * @param template - Template string
544
+ * @param context - Render context
545
+ * @returns Rendered HTML string
546
+ */
547
+ renderSync(template, context) {
548
+ if (!this.initialized || !this.hbs) {
549
+ throw new Error("HandlebarsRenderer not initialized. Call render() first or use initSync().");
550
+ }
551
+ let compiled = this.compiledTemplates.get(template);
552
+ if (!compiled) {
553
+ compiled = this.hbs.compile(template, {
554
+ strict: this.options.strict,
555
+ noEscape: !this.options.autoEscape
556
+ });
557
+ this.compiledTemplates.set(template, compiled);
558
+ }
559
+ const data = {
560
+ input: context.input ?? {},
561
+ output: context.output ?? {},
562
+ structuredContent: context.structuredContent,
563
+ ...context.input,
564
+ ...typeof context.output === "object" && context.output !== null ? context.output : {}
565
+ };
566
+ return compiled(data);
567
+ }
568
+ /**
569
+ * Initialize synchronously (for environments where Handlebars is already loaded).
570
+ */
571
+ initSync(handlebars) {
572
+ this.hbs = handlebars;
573
+ for (const [name, helper] of Object.entries(builtinHelpers)) {
574
+ this.hbs.registerHelper(name, helper);
575
+ }
576
+ if (this.options.helpers) {
577
+ for (const [name, helper] of Object.entries(this.options.helpers)) {
578
+ this.hbs.registerHelper(name, helper);
579
+ }
580
+ }
581
+ if (this.options.partials) {
582
+ for (const [name, template] of Object.entries(this.options.partials)) {
583
+ this.hbs.registerPartial(name, template);
584
+ }
585
+ }
586
+ this.initialized = true;
587
+ }
588
+ /**
589
+ * Register a custom helper.
590
+ *
591
+ * @param name - Helper name
592
+ * @param fn - Helper function
593
+ */
594
+ registerHelper(name, fn) {
595
+ if (this.hbs) {
596
+ this.hbs.registerHelper(name, fn);
597
+ }
598
+ if (!this.options.helpers) {
599
+ this.options.helpers = {};
600
+ }
601
+ this.options.helpers[name] = fn;
602
+ }
603
+ /**
604
+ * Register a partial template.
605
+ *
606
+ * @param name - Partial name
607
+ * @param template - Partial template string
608
+ */
609
+ registerPartial(name, template) {
610
+ if (this.hbs) {
611
+ this.hbs.registerPartial(name, template);
612
+ }
613
+ if (!this.options.partials) {
614
+ this.options.partials = {};
615
+ }
616
+ this.options.partials[name] = template;
617
+ }
618
+ /**
619
+ * Clear compiled template cache.
620
+ */
621
+ clearCache() {
622
+ this.compiledTemplates.clear();
623
+ }
624
+ /**
625
+ * Check if a template string contains Handlebars syntax.
626
+ *
627
+ * @param template - Template string to check
628
+ * @returns true if contains {{...}} syntax
629
+ */
630
+ static containsHandlebars(template) {
631
+ return /\{\{(?!!)[\s\S]*?\}\}/.test(template);
632
+ }
633
+ /**
634
+ * Check if the renderer is initialized.
635
+ */
636
+ get isInitialized() {
637
+ return this.initialized;
638
+ }
639
+ };
640
+ }
641
+ });
642
+
643
+ // libs/uipack/src/runtime/adapters/html.adapter.ts
644
+ var html_adapter_exports = {};
645
+ __export(html_adapter_exports, {
646
+ HtmlRendererAdapter: () => HtmlRendererAdapter,
647
+ createHtmlAdapter: () => createHtmlAdapter,
648
+ loadHtmlAdapter: () => loadHtmlAdapter
649
+ });
650
+ function createHtmlAdapter() {
651
+ return new HtmlRendererAdapter();
652
+ }
653
+ async function loadHtmlAdapter() {
654
+ return createHtmlAdapter();
655
+ }
656
+ var HtmlRendererAdapter;
657
+ var init_html_adapter = __esm({
658
+ "libs/uipack/src/runtime/adapters/html.adapter.ts"() {
659
+ "use strict";
660
+ HtmlRendererAdapter = class {
661
+ type = "html";
662
+ // Lazy-loaded Handlebars renderer
663
+ handlebarsRenderer = null;
664
+ /**
665
+ * Check if this adapter can handle the given content.
666
+ */
667
+ canHandle(content) {
668
+ return typeof content === "string";
669
+ }
670
+ /**
671
+ * Render HTML content to a string.
672
+ */
673
+ async render(content, context, _options) {
674
+ if (this.containsHandlebars(content)) {
675
+ return this.renderHandlebars(content, context);
676
+ }
677
+ return content;
678
+ }
679
+ /**
680
+ * Render HTML content directly to the DOM.
681
+ */
682
+ async renderToDOM(content, target, context, _options) {
683
+ try {
684
+ const html = await this.render(content, context);
685
+ target.innerHTML = html;
686
+ target.dispatchEvent(
687
+ new CustomEvent("frontmcp:rendered", {
688
+ bubbles: true,
689
+ detail: { type: "html", toolName: context.toolName }
690
+ })
691
+ );
692
+ return { success: true, html };
693
+ } catch (error) {
694
+ const message = error instanceof Error ? error.message : String(error);
695
+ console.error("[FrontMCP] HTML render failed:", message);
696
+ return { success: false, error: message };
697
+ }
698
+ }
699
+ /**
700
+ * Update rendered content with new data.
701
+ */
702
+ async update(target, context) {
703
+ const template = target.getAttribute("data-template");
704
+ if (template) {
705
+ return this.renderToDOM(template, target, context);
706
+ }
707
+ return { success: false, error: "No template stored for update" };
708
+ }
709
+ /**
710
+ * Clean up (no-op for HTML adapter).
711
+ */
712
+ destroy(_target) {
713
+ }
714
+ /**
715
+ * Check if content contains Handlebars syntax.
716
+ */
717
+ containsHandlebars(template) {
718
+ return /\{\{(?!!)[^}]*\}\}/.test(template);
719
+ }
720
+ /**
721
+ * Render Handlebars template.
722
+ */
723
+ async renderHandlebars(template, context) {
724
+ if (!this.handlebarsRenderer) {
725
+ try {
726
+ const handlebarsModule = await Promise.resolve().then(() => (init_handlebars(), handlebars_exports));
727
+ const { HandlebarsRenderer: HandlebarsRenderer2 } = handlebarsModule;
728
+ const renderer = new HandlebarsRenderer2();
729
+ this.handlebarsRenderer = {
730
+ render: (tmpl, ctx) => renderer.render(tmpl, ctx),
731
+ containsHandlebars: (tmpl) => HandlebarsRenderer2.containsHandlebars(tmpl)
732
+ };
733
+ } catch {
734
+ console.warn(
735
+ "[FrontMCP] Template contains Handlebars syntax but handlebars module not available. Ensure @frontmcp/ui/handlebars is properly bundled."
736
+ );
737
+ return template;
738
+ }
739
+ }
740
+ return this.handlebarsRenderer.render(template, {
741
+ input: context.input,
742
+ output: context.output
743
+ });
744
+ }
745
+ };
746
+ }
747
+ });
748
+
749
+ // libs/uipack/src/runtime/adapters/mdx.adapter.ts
750
+ var mdx_adapter_exports = {};
751
+ __export(mdx_adapter_exports, {
752
+ MdxRendererAdapter: () => MdxRendererAdapter,
753
+ createMdxAdapter: () => createMdxAdapter,
754
+ loadMdxAdapter: () => loadMdxAdapter
755
+ });
756
+ function createMdxAdapter() {
757
+ return new MdxRendererAdapter();
758
+ }
759
+ async function loadMdxAdapter() {
760
+ return createMdxAdapter();
761
+ }
762
+ var MdxRendererAdapter;
763
+ var init_mdx_adapter = __esm({
764
+ "libs/uipack/src/runtime/adapters/mdx.adapter.ts"() {
765
+ "use strict";
766
+ MdxRendererAdapter = class {
767
+ type = "mdx";
768
+ // Lazy-loaded MDX runtime
769
+ mdxRuntime = null;
770
+ loadPromise = null;
771
+ /**
772
+ * Check if this adapter can handle the given content.
773
+ */
774
+ canHandle(content) {
775
+ if (typeof content !== "string") {
776
+ return false;
777
+ }
778
+ return /^---[\s\S]*?---/.test(content) || // Frontmatter
779
+ /<[A-Z][a-zA-Z0-9]*[\s/>]/.test(content) || // JSX component tags
780
+ /^import\s+/.test(content) || // Import statements
781
+ /^export\s+/.test(content) || // Export statements
782
+ content.includes("```jsx") || // Code blocks with JSX
783
+ content.includes("```tsx");
784
+ }
785
+ /**
786
+ * Render MDX content to a string.
787
+ */
788
+ async render(content, context, _options) {
789
+ try {
790
+ await this.ensureMdxLoaded();
791
+ if (!this.mdxRuntime) {
792
+ return this.renderMarkdown(content, context);
793
+ }
794
+ const compiled = await this.compileMdx(content, context);
795
+ return compiled;
796
+ } catch (error) {
797
+ console.error("[FrontMCP] MDX render failed:", error);
798
+ return this.renderMarkdown(content, context);
799
+ }
800
+ }
801
+ /**
802
+ * Render MDX content directly to the DOM.
803
+ */
804
+ async renderToDOM(content, target, context, _options) {
805
+ try {
806
+ const html = await this.render(content, context);
807
+ target.innerHTML = html;
808
+ target.dispatchEvent(
809
+ new CustomEvent("frontmcp:rendered", {
810
+ bubbles: true,
811
+ detail: { type: "mdx", toolName: context.toolName }
812
+ })
813
+ );
814
+ return { success: true, html };
815
+ } catch (error) {
816
+ const message = error instanceof Error ? error.message : String(error);
817
+ console.error("[FrontMCP] MDX render to DOM failed:", message);
818
+ return { success: false, error: message };
819
+ }
820
+ }
821
+ /**
822
+ * Hydrate existing SSR content.
823
+ * MDX hydration follows React patterns.
824
+ */
825
+ async hydrate(target, context, options) {
826
+ const content = target.getAttribute("data-mdx-source");
827
+ if (content) {
828
+ return this.renderToDOM(content, target, context, options);
829
+ }
830
+ return { success: true };
831
+ }
832
+ /**
833
+ * Update rendered MDX content with new data.
834
+ */
835
+ async update(target, context) {
836
+ const source = target.getAttribute("data-mdx-source");
837
+ if (source) {
838
+ return this.renderToDOM(source, target, context);
839
+ }
840
+ return { success: false, error: "No MDX source stored for update" };
841
+ }
842
+ /**
843
+ * Clean up (no-op for MDX adapter).
844
+ */
845
+ destroy(_target) {
846
+ }
847
+ /**
848
+ * Ensure MDX runtime is loaded.
849
+ */
850
+ async ensureMdxLoaded() {
851
+ if (this.mdxRuntime) {
852
+ return;
853
+ }
854
+ if (this.loadPromise) {
855
+ return this.loadPromise;
856
+ }
857
+ this.loadPromise = this.loadMdx();
858
+ return this.loadPromise;
859
+ }
860
+ /**
861
+ * Load MDX runtime.
862
+ */
863
+ async loadMdx() {
864
+ try {
865
+ const mdxModule = await import(
866
+ /* webpackIgnore: true */
867
+ "@mdx-js/mdx"
868
+ );
869
+ this.mdxRuntime = {
870
+ run: async () => ({ default: null }),
871
+ compile: mdxModule.compile
872
+ };
873
+ } catch {
874
+ console.warn(
875
+ "[FrontMCP] MDX runtime not available. Install @mdx-js/mdx for full MDX support, or use pre-compiled MDX."
876
+ );
877
+ }
878
+ }
879
+ /**
880
+ * Compile MDX content.
881
+ */
882
+ async compileMdx(source, context) {
883
+ if (!this.mdxRuntime?.compile) {
884
+ return this.renderMarkdown(source, context);
885
+ }
886
+ try {
887
+ const compiled = await this.mdxRuntime.compile(source, {
888
+ outputFormat: "function-body",
889
+ development: false
890
+ });
891
+ return `<div class="mdx-content" data-compiled="true">${this.renderMarkdown(source, context)}</div>`;
892
+ } catch (error) {
893
+ console.warn("[FrontMCP] MDX compilation failed, falling back to markdown:", error);
894
+ return this.renderMarkdown(source, context);
895
+ }
896
+ }
897
+ /**
898
+ * Basic markdown rendering (fallback).
899
+ */
900
+ renderMarkdown(source, context) {
901
+ let html = source;
902
+ html = html.replace(/&/g, "&amp;").replace(/</g, "&lt;").replace(/>/g, "&gt;");
903
+ html = html.replace(/^---[\s\S]*?---\n?/, "");
904
+ html = html.replace(/^######\s+(.+)$/gm, "<h6>$1</h6>");
905
+ html = html.replace(/^#####\s+(.+)$/gm, "<h5>$1</h5>");
906
+ html = html.replace(/^####\s+(.+)$/gm, "<h4>$1</h4>");
907
+ html = html.replace(/^###\s+(.+)$/gm, "<h3>$1</h3>");
908
+ html = html.replace(/^##\s+(.+)$/gm, "<h2>$1</h2>");
909
+ html = html.replace(/^#\s+(.+)$/gm, "<h1>$1</h1>");
910
+ html = html.replace(/\*\*\*(.+?)\*\*\*/g, "<strong><em>$1</em></strong>");
911
+ html = html.replace(/\*\*(.+?)\*\*/g, "<strong>$1</strong>");
912
+ html = html.replace(/\*(.+?)\*/g, "<em>$1</em>");
913
+ html = html.replace(/___(.+?)___/g, "<strong><em>$1</em></strong>");
914
+ html = html.replace(/__(.+?)__/g, "<strong>$1</strong>");
915
+ html = html.replace(/_(.+?)_/g, "<em>$1</em>");
916
+ html = html.replace(/```(\w+)?\n([\s\S]*?)```/g, '<pre><code class="language-$1">$2</code></pre>');
917
+ html = html.replace(/`([^`]+)`/g, "<code>$1</code>");
918
+ html = html.replace(/\[([^\]]+)\]\(([^)]+)\)/g, '<a href="$2">$1</a>');
919
+ html = html.replace(/^\s*[-*]\s+(.+)$/gm, "<li>$1</li>");
920
+ html = html.replace(/(<li>.*<\/li>\n?)+/g, "<ul>$&</ul>");
921
+ html = html.replace(/^\s*\d+\.\s+(.+)$/gm, "<li>$1</li>");
922
+ html = html.split(/\n\n+/).map((para) => {
923
+ const trimmed = para.trim();
924
+ if (!trimmed || trimmed.startsWith("<h") || trimmed.startsWith("<ul") || trimmed.startsWith("<ol") || trimmed.startsWith("<pre")) {
925
+ return trimmed;
926
+ }
927
+ return `<p>${trimmed}</p>`;
928
+ }).join("\n");
929
+ html = html.replace(/\{\{output\.(\w+)\}\}/g, (_match, key) => {
930
+ const value = context.output?.[key];
931
+ return value !== void 0 ? String(value) : "";
932
+ });
933
+ html = html.replace(/\{\{input\.(\w+)\}\}/g, (_match, key) => {
934
+ const value = context.input?.[key];
935
+ return value !== void 0 ? String(value) : "";
936
+ });
937
+ return `<div class="markdown-content">${html}</div>`;
938
+ }
939
+ };
940
+ }
941
+ });
942
+
943
+ // libs/uipack/src/bridge-runtime/iife-generator.ts
944
+ function generateBridgeIIFE(options = {}) {
945
+ const { debug = false, trustedOrigins = [], minify = false } = options;
946
+ const adapters = options.adapters || ["openai", "ext-apps", "claude", "gemini", "generic"];
947
+ const parts = [];
948
+ parts.push("(function() {");
949
+ parts.push('"use strict";');
950
+ parts.push("");
951
+ if (debug) {
952
+ parts.push('function log(msg) { console.log("[FrontMcpBridge] " + msg); }');
953
+ } else {
954
+ parts.push("function log() {}");
955
+ }
956
+ parts.push("");
957
+ parts.push("var DEFAULT_SAFE_AREA = { top: 0, bottom: 0, left: 0, right: 0 };");
958
+ parts.push("");
959
+ parts.push(generateContextDetection());
960
+ parts.push("");
961
+ parts.push(generateBaseCapabilities());
962
+ parts.push("");
963
+ if (adapters.includes("openai")) {
964
+ parts.push(generateOpenAIAdapter());
965
+ parts.push("");
966
+ }
967
+ if (adapters.includes("ext-apps")) {
968
+ parts.push(generateExtAppsAdapter(trustedOrigins));
969
+ parts.push("");
970
+ }
971
+ if (adapters.includes("claude")) {
972
+ parts.push(generateClaudeAdapter());
973
+ parts.push("");
974
+ }
975
+ if (adapters.includes("gemini")) {
976
+ parts.push(generateGeminiAdapter());
977
+ parts.push("");
978
+ }
979
+ if (adapters.includes("generic")) {
980
+ parts.push(generateGenericAdapter());
981
+ parts.push("");
982
+ }
983
+ parts.push(generatePlatformDetection(adapters));
984
+ parts.push("");
985
+ parts.push(generateBridgeClass());
986
+ parts.push("");
987
+ parts.push("var bridge = new FrontMcpBridge();");
988
+ parts.push("bridge.initialize().then(function() {");
989
+ parts.push(' log("Bridge initialized with adapter: " + bridge.adapterId);');
990
+ parts.push(' window.dispatchEvent(new CustomEvent("bridge:ready", { detail: { adapter: bridge.adapterId } }));');
991
+ parts.push("}).catch(function(err) {");
992
+ parts.push(' console.error("[FrontMcpBridge] Init failed:", err);');
993
+ parts.push(' window.dispatchEvent(new CustomEvent("bridge:error", { detail: { error: err } }));');
994
+ parts.push("});");
995
+ parts.push("");
996
+ parts.push("window.FrontMcpBridge = bridge;");
997
+ parts.push("})();");
998
+ const code = parts.join("\n");
999
+ if (minify) {
1000
+ return minifyJS(code);
1001
+ }
1002
+ return code;
1003
+ }
1004
+ function generateContextDetection() {
1005
+ return `
1006
+ function detectTheme() {
1007
+ if (typeof window !== 'undefined' && window.matchMedia) {
1008
+ return window.matchMedia('(prefers-color-scheme: dark)').matches ? 'dark' : 'light';
1009
+ }
1010
+ return 'light';
1011
+ }
1012
+
1013
+ function detectLocale() {
1014
+ if (typeof navigator !== 'undefined') {
1015
+ return navigator.language || 'en-US';
1016
+ }
1017
+ return 'en-US';
1018
+ }
1019
+
1020
+ function detectUserAgent() {
1021
+ if (typeof navigator === 'undefined') {
1022
+ return { type: 'web', hover: true, touch: false };
1023
+ }
1024
+ var ua = navigator.userAgent || '';
1025
+ var isMobile = /iPhone|iPad|iPod|Android/i.test(ua);
1026
+ var hasTouch = 'ontouchstart' in window || navigator.maxTouchPoints > 0;
1027
+ var hasHover = window.matchMedia && window.matchMedia('(hover: hover)').matches;
1028
+ return { type: isMobile ? 'mobile' : 'web', hover: hasHover !== false, touch: hasTouch };
1029
+ }
1030
+
1031
+ function detectViewport() {
1032
+ if (typeof window !== 'undefined') {
1033
+ return { width: window.innerWidth, height: window.innerHeight };
1034
+ }
1035
+ return undefined;
1036
+ }
1037
+
1038
+ function readInjectedData() {
1039
+ var data = { toolInput: {}, toolOutput: undefined, structuredContent: undefined };
1040
+ if (typeof window !== 'undefined') {
1041
+ if (window.__mcpToolInput) data.toolInput = window.__mcpToolInput;
1042
+ if (window.__mcpToolOutput) data.toolOutput = window.__mcpToolOutput;
1043
+ if (window.__mcpStructuredContent) data.structuredContent = window.__mcpStructuredContent;
1044
+ }
1045
+ return data;
1046
+ }
1047
+ `.trim();
1048
+ }
1049
+ function generateBaseCapabilities() {
1050
+ return `
1051
+ var DEFAULT_CAPABILITIES = {
1052
+ canCallTools: false,
1053
+ canSendMessages: false,
1054
+ canOpenLinks: false,
1055
+ canPersistState: true,
1056
+ hasNetworkAccess: true,
1057
+ supportsDisplayModes: false,
1058
+ supportsTheme: true
1059
+ };
1060
+ `.trim();
1061
+ }
1062
+ function generateOpenAIAdapter() {
1063
+ return `
1064
+ var OpenAIAdapter = {
1065
+ id: 'openai',
1066
+ name: 'OpenAI ChatGPT',
1067
+ priority: 100,
1068
+ capabilities: Object.assign({}, DEFAULT_CAPABILITIES, {
1069
+ canCallTools: true,
1070
+ canSendMessages: true,
1071
+ canOpenLinks: true,
1072
+ supportsDisplayModes: true
1073
+ }),
1074
+ canHandle: function() {
1075
+ if (typeof window === 'undefined') return false;
1076
+ // Check for window.openai.callTool (the actual OpenAI SDK API)
1077
+ if (window.openai && typeof window.openai.callTool === 'function') return true;
1078
+ // Also check if we're being injected with tool metadata (OpenAI injects toolOutput)
1079
+ if (window.openai && (window.openai.toolOutput !== undefined || window.openai.toolInput !== undefined)) return true;
1080
+ return false;
1081
+ },
1082
+ initialize: function(context) {
1083
+ var sdk = window.openai;
1084
+ context.sdk = sdk;
1085
+ // OpenAI SDK exposes theme and displayMode directly as properties
1086
+ if (sdk.theme) {
1087
+ context.hostContext.theme = sdk.theme;
1088
+ }
1089
+ if (sdk.displayMode) {
1090
+ context.hostContext.displayMode = sdk.displayMode;
1091
+ }
1092
+ // Note: OpenAI SDK does not have an onContextChange equivalent
1093
+ return Promise.resolve();
1094
+ },
1095
+ callTool: function(context, name, args) {
1096
+ return context.sdk.callTool(name, args);
1097
+ },
1098
+ sendMessage: function(context, content) {
1099
+ if (typeof context.sdk.sendFollowUpMessage === 'function') {
1100
+ return context.sdk.sendFollowUpMessage(content);
1101
+ }
1102
+ return Promise.reject(new Error('Messages not supported'));
1103
+ },
1104
+ openLink: function(context, url) {
1105
+ window.open(url, '_blank', 'noopener,noreferrer');
1106
+ return Promise.resolve();
1107
+ },
1108
+ requestDisplayMode: function(context, mode) {
1109
+ return Promise.resolve();
1110
+ },
1111
+ requestClose: function(context) {
1112
+ return Promise.resolve();
1113
+ }
1114
+ };
1115
+ `.trim();
1116
+ }
1117
+ function generateExtAppsAdapter(trustedOrigins) {
1118
+ const originsArray = trustedOrigins.length > 0 ? JSON.stringify(trustedOrigins) : "[]";
1119
+ return `
1120
+ var ExtAppsAdapter = {
1121
+ id: 'ext-apps',
1122
+ name: 'ext-apps (SEP-1865)',
1123
+ priority: 80,
1124
+ capabilities: Object.assign({}, DEFAULT_CAPABILITIES, { canPersistState: true, hasNetworkAccess: true }),
1125
+ trustedOrigins: ${originsArray},
1126
+ trustedOrigin: null,
1127
+ pendingRequests: {},
1128
+ requestId: 0,
1129
+ hostCapabilities: {},
1130
+ canHandle: function() {
1131
+ if (typeof window === 'undefined') return false;
1132
+ if (window.parent === window) return false;
1133
+ // Check for OpenAI SDK (window.openai.callTool) - defer to OpenAIAdapter
1134
+ if (window.openai && typeof window.openai.callTool === 'function') return false;
1135
+ if (window.__mcpPlatform === 'ext-apps') return true;
1136
+ return true;
1137
+ },
1138
+ initialize: function(context) {
1139
+ var self = this;
1140
+ context.extApps = this;
1141
+
1142
+ window.addEventListener('message', function(event) {
1143
+ self.handleMessage(context, event);
1144
+ });
1145
+
1146
+ return self.performHandshake(context);
1147
+ },
1148
+ handleMessage: function(context, event) {
1149
+ if (!this.isOriginTrusted(event.origin)) return;
1150
+ var data = event.data;
1151
+ if (!data || typeof data !== 'object' || data.jsonrpc !== '2.0') return;
1152
+
1153
+ if ('id' in data && (data.result !== undefined || data.error !== undefined)) {
1154
+ var pending = this.pendingRequests[data.id];
1155
+ if (pending) {
1156
+ clearTimeout(pending.timeout);
1157
+ delete this.pendingRequests[data.id];
1158
+ if (data.error) {
1159
+ pending.reject(new Error(data.error.message + ' (code: ' + data.error.code + ')'));
1160
+ } else {
1161
+ pending.resolve(data.result);
1162
+ }
1163
+ }
1164
+ return;
1165
+ }
1166
+
1167
+ if ('method' in data && !('id' in data)) {
1168
+ this.handleNotification(context, data);
1169
+ }
1170
+ },
1171
+ handleNotification: function(context, notification) {
1172
+ var params = notification.params || {};
1173
+ switch (notification.method) {
1174
+ case 'ui/notifications/tool-input':
1175
+ context.toolInput = params.arguments || {};
1176
+ window.dispatchEvent(new CustomEvent('tool:input', { detail: { arguments: context.toolInput } }));
1177
+ break;
1178
+ case 'ui/notifications/tool-result':
1179
+ context.toolOutput = params.content;
1180
+ context.structuredContent = params.structuredContent;
1181
+ context.notifyToolResult(params.content);
1182
+ window.dispatchEvent(new CustomEvent('tool:result', { detail: params }));
1183
+ break;
1184
+ case 'ui/notifications/host-context-changed':
1185
+ Object.assign(context.hostContext, params);
1186
+ context.notifyContextChange(params);
1187
+ break;
1188
+ }
1189
+ },
1190
+ isOriginTrusted: function(origin) {
1191
+ if (this.trustedOrigins.length > 0) {
1192
+ return this.trustedOrigins.indexOf(origin) !== -1;
1193
+ }
1194
+ // When no trusted origins configured, trust first message origin (trust-on-first-use).
1195
+ // SECURITY WARNING: This creates a race condition - whichever iframe sends the first
1196
+ // message establishes permanent trust. For production, always configure trustedOrigins.
1197
+ if (!this.trustedOrigin) {
1198
+ if (window.parent !== window && origin) {
1199
+ this.trustedOrigin = origin;
1200
+ return true;
1201
+ }
1202
+ return false;
1203
+ }
1204
+ return this.trustedOrigin === origin;
1205
+ },
1206
+ sendRequest: function(method, params) {
1207
+ var self = this;
1208
+ return new Promise(function(resolve, reject) {
1209
+ // Security: Require trusted origin before sending requests to prevent message leaks
1210
+ if (!self.trustedOrigin && self.trustedOrigins.length === 0) {
1211
+ reject(new Error('Cannot send request: no trusted origin established'));
1212
+ return;
1213
+ }
1214
+
1215
+ var id = ++self.requestId;
1216
+ var timeout = setTimeout(function() {
1217
+ delete self.pendingRequests[id];
1218
+ reject(new Error('Request ' + method + ' timed out'));
1219
+ }, 10000);
1220
+
1221
+ self.pendingRequests[id] = { resolve: resolve, reject: reject, timeout: timeout };
1222
+
1223
+ var targetOrigin = self.trustedOrigin || self.trustedOrigins[0];
1224
+ window.parent.postMessage({ jsonrpc: '2.0', id: id, method: method, params: params }, targetOrigin);
1225
+ });
1226
+ },
1227
+ performHandshake: function(context) {
1228
+ var self = this;
1229
+ var params = {
1230
+ appInfo: { name: 'FrontMCP Widget', version: '1.0.0' },
1231
+ appCapabilities: { tools: { listChanged: false } },
1232
+ protocolVersion: '2024-11-05'
1233
+ };
1234
+
1235
+ return this.sendRequest('ui/initialize', params).then(function(result) {
1236
+ self.hostCapabilities = result.hostCapabilities || {};
1237
+ self.capabilities = Object.assign({}, self.capabilities, {
1238
+ canCallTools: Boolean(self.hostCapabilities.serverToolProxy),
1239
+ canSendMessages: true,
1240
+ canOpenLinks: Boolean(self.hostCapabilities.openLink),
1241
+ supportsDisplayModes: true
1242
+ });
1243
+ if (result.hostContext) {
1244
+ Object.assign(context.hostContext, result.hostContext);
1245
+ }
1246
+ });
1247
+ },
1248
+ callTool: function(context, name, args) {
1249
+ if (!this.hostCapabilities.serverToolProxy) {
1250
+ return Promise.reject(new Error('Server tool proxy not supported'));
1251
+ }
1252
+ return this.sendRequest('ui/callServerTool', { name: name, arguments: args });
1253
+ },
1254
+ sendMessage: function(context, content) {
1255
+ return this.sendRequest('ui/message', { content: content });
1256
+ },
1257
+ openLink: function(context, url) {
1258
+ if (!this.hostCapabilities.openLink) {
1259
+ window.open(url, '_blank', 'noopener,noreferrer');
1260
+ return Promise.resolve();
1261
+ }
1262
+ return this.sendRequest('ui/openLink', { url: url });
1263
+ },
1264
+ requestDisplayMode: function(context, mode) {
1265
+ return this.sendRequest('ui/setDisplayMode', { mode: mode });
1266
+ },
1267
+ requestClose: function(context) {
1268
+ return this.sendRequest('ui/close', {});
1269
+ }
1270
+ };
1271
+ `.trim();
1272
+ }
1273
+ function generateClaudeAdapter() {
1274
+ return `
1275
+ var ClaudeAdapter = {
1276
+ id: 'claude',
1277
+ name: 'Claude (Anthropic)',
1278
+ priority: 60,
1279
+ capabilities: Object.assign({}, DEFAULT_CAPABILITIES, {
1280
+ canCallTools: false,
1281
+ canSendMessages: false,
1282
+ canOpenLinks: true,
1283
+ hasNetworkAccess: false,
1284
+ supportsDisplayModes: false
1285
+ }),
1286
+ canHandle: function() {
1287
+ if (typeof window === 'undefined') return false;
1288
+ if (window.__mcpPlatform === 'claude') return true;
1289
+ if (window.claude) return true;
1290
+ if (window.__claudeArtifact) return true;
1291
+ if (typeof location !== 'undefined') {
1292
+ var href = location.href;
1293
+ if (href.indexOf('claude.ai') !== -1 || href.indexOf('anthropic.com') !== -1) return true;
1294
+ }
1295
+ return false;
1296
+ },
1297
+ initialize: function(context) {
1298
+ return Promise.resolve();
1299
+ },
1300
+ callTool: function() {
1301
+ return Promise.reject(new Error('Tool calls not supported in Claude'));
1302
+ },
1303
+ sendMessage: function() {
1304
+ return Promise.reject(new Error('Messages not supported in Claude'));
1305
+ },
1306
+ openLink: function(context, url) {
1307
+ window.open(url, '_blank', 'noopener,noreferrer');
1308
+ return Promise.resolve();
1309
+ },
1310
+ requestDisplayMode: function() {
1311
+ return Promise.resolve();
1312
+ },
1313
+ requestClose: function() {
1314
+ return Promise.resolve();
1315
+ }
1316
+ };
1317
+ `.trim();
1318
+ }
1319
+ function generateGeminiAdapter() {
1320
+ return `
1321
+ var GeminiAdapter = {
1322
+ id: 'gemini',
1323
+ name: 'Google Gemini',
1324
+ priority: 40,
1325
+ capabilities: Object.assign({}, DEFAULT_CAPABILITIES, {
1326
+ canOpenLinks: true,
1327
+ hasNetworkAccess: true
1328
+ }),
1329
+ canHandle: function() {
1330
+ if (typeof window === 'undefined') return false;
1331
+ if (window.__mcpPlatform === 'gemini') return true;
1332
+ if (window.gemini) return true;
1333
+ if (typeof location !== 'undefined') {
1334
+ var href = location.href;
1335
+ if (href.indexOf('gemini.google.com') !== -1 || href.indexOf('bard.google.com') !== -1) return true;
1336
+ }
1337
+ return false;
1338
+ },
1339
+ initialize: function(context) {
1340
+ if (window.gemini && window.gemini.ui && window.gemini.ui.getTheme) {
1341
+ context.hostContext.theme = window.gemini.ui.getTheme() === 'dark' ? 'dark' : 'light';
1342
+ }
1343
+ return Promise.resolve();
1344
+ },
1345
+ callTool: function() {
1346
+ return Promise.reject(new Error('Tool calls not supported in Gemini'));
1347
+ },
1348
+ sendMessage: function(context, content) {
1349
+ if (window.gemini && window.gemini.ui && window.gemini.ui.sendMessage) {
1350
+ return window.gemini.ui.sendMessage(content);
1351
+ }
1352
+ return Promise.reject(new Error('Messages not supported in Gemini'));
1353
+ },
1354
+ openLink: function(context, url) {
1355
+ if (window.gemini && window.gemini.ui && window.gemini.ui.openLink) {
1356
+ return window.gemini.ui.openLink(url);
1357
+ }
1358
+ window.open(url, '_blank', 'noopener,noreferrer');
1359
+ return Promise.resolve();
1360
+ },
1361
+ requestDisplayMode: function() {
1362
+ return Promise.resolve();
1363
+ },
1364
+ requestClose: function() {
1365
+ return Promise.resolve();
1366
+ }
1367
+ };
1368
+ `.trim();
1369
+ }
1370
+ function generateGenericAdapter() {
1371
+ return `
1372
+ var GenericAdapter = {
1373
+ id: 'generic',
1374
+ name: 'Generic Web',
1375
+ priority: 0,
1376
+ capabilities: Object.assign({}, DEFAULT_CAPABILITIES, {
1377
+ canOpenLinks: true,
1378
+ hasNetworkAccess: true
1379
+ }),
1380
+ canHandle: function() {
1381
+ return typeof window !== 'undefined';
1382
+ },
1383
+ initialize: function(context) {
1384
+ return Promise.resolve();
1385
+ },
1386
+ callTool: function() {
1387
+ return Promise.reject(new Error('Tool calls not supported'));
1388
+ },
1389
+ sendMessage: function() {
1390
+ return Promise.reject(new Error('Messages not supported'));
1391
+ },
1392
+ openLink: function(context, url) {
1393
+ window.open(url, '_blank', 'noopener,noreferrer');
1394
+ return Promise.resolve();
1395
+ },
1396
+ requestDisplayMode: function() {
1397
+ return Promise.resolve();
1398
+ },
1399
+ requestClose: function() {
1400
+ return Promise.resolve();
1401
+ }
1402
+ };
1403
+ `.trim();
1404
+ }
1405
+ function generatePlatformDetection(adapters) {
1406
+ const adapterVars = adapters.map((a) => {
1407
+ switch (a) {
1408
+ case "openai":
1409
+ return "OpenAIAdapter";
1410
+ case "ext-apps":
1411
+ return "ExtAppsAdapter";
1412
+ case "claude":
1413
+ return "ClaudeAdapter";
1414
+ case "gemini":
1415
+ return "GeminiAdapter";
1416
+ case "generic":
1417
+ return "GenericAdapter";
1418
+ default:
1419
+ return "";
1420
+ }
1421
+ }).filter(Boolean);
1422
+ return `
1423
+ var ADAPTERS = [${adapterVars.join(", ")}].sort(function(a, b) { return b.priority - a.priority; });
1424
+
1425
+ function detectPlatform() {
1426
+ for (var i = 0; i < ADAPTERS.length; i++) {
1427
+ if (ADAPTERS[i].canHandle()) {
1428
+ log('Detected platform: ' + ADAPTERS[i].id);
1429
+ return ADAPTERS[i];
1430
+ }
1431
+ }
1432
+ log('No platform detected, using generic');
1433
+ return GenericAdapter;
1434
+ }
1435
+ `.trim();
1436
+ }
1437
+ function generateBridgeClass() {
1438
+ return `
1439
+ function FrontMcpBridge() {
1440
+ this._adapter = null;
1441
+ this._initialized = false;
1442
+ this._context = {
1443
+ hostContext: {
1444
+ theme: detectTheme(),
1445
+ displayMode: 'inline',
1446
+ locale: detectLocale(),
1447
+ userAgent: detectUserAgent(),
1448
+ safeArea: DEFAULT_SAFE_AREA,
1449
+ viewport: detectViewport()
1450
+ },
1451
+ toolInput: {},
1452
+ toolOutput: undefined,
1453
+ structuredContent: undefined,
1454
+ widgetState: {},
1455
+ contextListeners: [],
1456
+ toolResultListeners: [],
1457
+ notifyContextChange: function(changes) {
1458
+ Object.assign(this.hostContext, changes);
1459
+ for (var i = 0; i < this.contextListeners.length; i++) {
1460
+ try { this.contextListeners[i](changes); } catch(e) {}
1461
+ }
1462
+ },
1463
+ notifyToolResult: function(result) {
1464
+ this.toolOutput = result;
1465
+ for (var i = 0; i < this.toolResultListeners.length; i++) {
1466
+ try { this.toolResultListeners[i](result); } catch(e) {}
1467
+ }
1468
+ }
1469
+ };
1470
+
1471
+ var injected = readInjectedData();
1472
+ this._context.toolInput = injected.toolInput;
1473
+ this._context.toolOutput = injected.toolOutput;
1474
+ this._context.structuredContent = injected.structuredContent;
1475
+
1476
+ this._loadWidgetState();
1477
+ }
1478
+
1479
+ FrontMcpBridge.prototype._loadWidgetState = function() {
1480
+ try {
1481
+ var key = 'frontmcp:widget:' + (window.__mcpToolName || 'unknown');
1482
+ var stored = localStorage.getItem(key);
1483
+ if (stored) this._context.widgetState = JSON.parse(stored);
1484
+ } catch(e) {}
1485
+ };
1486
+
1487
+ FrontMcpBridge.prototype._saveWidgetState = function() {
1488
+ try {
1489
+ var key = 'frontmcp:widget:' + (window.__mcpToolName || 'unknown');
1490
+ localStorage.setItem(key, JSON.stringify(this._context.widgetState));
1491
+ } catch(e) {}
1492
+ };
1493
+
1494
+ FrontMcpBridge.prototype.initialize = function() {
1495
+ if (this._initialized) return Promise.resolve();
1496
+ var self = this;
1497
+ this._adapter = detectPlatform();
1498
+ return this._adapter.initialize(this._context).then(function() {
1499
+ self._initialized = true;
1500
+ // Set up the data-tool-call click handler after initialization
1501
+ self._setupDataToolCallHandler();
1502
+ });
1503
+ };
1504
+
1505
+ Object.defineProperty(FrontMcpBridge.prototype, 'initialized', { get: function() { return this._initialized; } });
1506
+ Object.defineProperty(FrontMcpBridge.prototype, 'adapterId', { get: function() { return this._adapter ? this._adapter.id : undefined; } });
1507
+ Object.defineProperty(FrontMcpBridge.prototype, 'capabilities', { get: function() { return this._adapter ? this._adapter.capabilities : DEFAULT_CAPABILITIES; } });
1508
+
1509
+ FrontMcpBridge.prototype.getTheme = function() { return this._context.hostContext.theme; };
1510
+ FrontMcpBridge.prototype.getDisplayMode = function() { return this._context.hostContext.displayMode; };
1511
+ FrontMcpBridge.prototype.getToolInput = function() { return this._context.toolInput; };
1512
+ FrontMcpBridge.prototype.getToolOutput = function() { return this._context.toolOutput; };
1513
+ FrontMcpBridge.prototype.getStructuredContent = function() { return this._context.structuredContent; };
1514
+ FrontMcpBridge.prototype.getWidgetState = function() { return this._context.widgetState; };
1515
+ FrontMcpBridge.prototype.getHostContext = function() { return Object.assign({}, this._context.hostContext); };
1516
+ FrontMcpBridge.prototype.hasCapability = function(cap) { return this._adapter && this._adapter.capabilities[cap] === true; };
1517
+
1518
+ // Get tool response metadata (platform-agnostic)
1519
+ // Used by inline mode widgets to detect when ui/html arrives
1520
+ FrontMcpBridge.prototype.getToolResponseMetadata = function() {
1521
+ // OpenAI injects toolResponseMetadata for widget-producing tools
1522
+ if (typeof window !== 'undefined' && window.openai && window.openai.toolResponseMetadata) {
1523
+ return window.openai.toolResponseMetadata;
1524
+ }
1525
+ // Claude (future support)
1526
+ if (typeof window !== 'undefined' && window.claude && window.claude.toolResponseMetadata) {
1527
+ return window.claude.toolResponseMetadata;
1528
+ }
1529
+ // FrontMCP direct injection (for testing/ext-apps)
1530
+ if (typeof window !== 'undefined' && window.__mcpToolResponseMetadata) {
1531
+ return window.__mcpToolResponseMetadata;
1532
+ }
1533
+ return null;
1534
+ };
1535
+
1536
+ // Subscribe to tool response metadata changes (for inline mode injection)
1537
+ FrontMcpBridge.prototype.onToolResponseMetadata = function(callback) {
1538
+ var self = this;
1539
+ var called = false;
1540
+
1541
+ // Check if already available
1542
+ var existing = self.getToolResponseMetadata();
1543
+ if (existing) {
1544
+ called = true;
1545
+ callback(existing);
1546
+ }
1547
+
1548
+ // Set up property interceptors for OpenAI
1549
+ if (typeof window !== 'undefined') {
1550
+ // OpenAI: Intercept toolResponseMetadata assignment
1551
+ if (!window.__frontmcpMetadataIntercepted) {
1552
+ window.__frontmcpMetadataIntercepted = true;
1553
+ window.__frontmcpMetadataCallbacks = [];
1554
+
1555
+ // Create openai object if it doesn't exist
1556
+ if (!window.openai) window.openai = {};
1557
+
1558
+ var originalMetadata = window.openai.toolResponseMetadata;
1559
+ Object.defineProperty(window.openai, 'toolResponseMetadata', {
1560
+ get: function() { return originalMetadata; },
1561
+ set: function(val) {
1562
+ originalMetadata = val;
1563
+ log('toolResponseMetadata set, notifying ' + window.__frontmcpMetadataCallbacks.length + ' listeners');
1564
+ for (var i = 0; i < window.__frontmcpMetadataCallbacks.length; i++) {
1565
+ try { window.__frontmcpMetadataCallbacks[i](val); } catch(e) {}
1566
+ }
1567
+ },
1568
+ configurable: true
1569
+ });
1570
+ }
1571
+
1572
+ // Register callback wrapper (store reference for unsubscribe)
1573
+ var wrapper = function(metadata) {
1574
+ if (!called) {
1575
+ called = true;
1576
+ callback(metadata);
1577
+ }
1578
+ };
1579
+ window.__frontmcpMetadataCallbacks.push(wrapper);
1580
+
1581
+ // Return unsubscribe function that removes the wrapper (not the original callback)
1582
+ return function() {
1583
+ if (window.__frontmcpMetadataCallbacks) {
1584
+ var idx = window.__frontmcpMetadataCallbacks.indexOf(wrapper);
1585
+ if (idx !== -1) window.__frontmcpMetadataCallbacks.splice(idx, 1);
1586
+ }
1587
+ };
1588
+ }
1589
+
1590
+ // Return no-op unsubscribe for non-window environments
1591
+ return function() {};
1592
+ };
1593
+
1594
+ FrontMcpBridge.prototype.callTool = function(name, args) {
1595
+ // Priority 1: Direct OpenAI SDK call (most reliable in OpenAI iframe)
1596
+ // This bypasses adapter abstraction for maximum compatibility
1597
+ if (typeof window !== 'undefined' && window.openai && typeof window.openai.callTool === 'function') {
1598
+ log('callTool: Using OpenAI SDK directly');
1599
+ return window.openai.callTool(name, args);
1600
+ }
1601
+
1602
+ // Priority 2: Use adapter (if initialized and supports tool calls)
1603
+ if (this._adapter && this._adapter.capabilities && this._adapter.capabilities.canCallTools) {
1604
+ log('callTool: Using adapter ' + this._adapter.id);
1605
+ return this._adapter.callTool(this._context, name, args);
1606
+ }
1607
+
1608
+ // Not initialized or no tool support
1609
+ if (!this._adapter) {
1610
+ return Promise.reject(new Error('Bridge not initialized. Wait for bridge:ready event.'));
1611
+ }
1612
+ return Promise.reject(new Error('Tool calls not supported on this platform (' + this._adapter.id + ')'));
1613
+ };
1614
+
1615
+ FrontMcpBridge.prototype.sendMessage = function(content) {
1616
+ if (!this._adapter) return Promise.reject(new Error('Not initialized'));
1617
+ return this._adapter.sendMessage(this._context, content);
1618
+ };
1619
+
1620
+ FrontMcpBridge.prototype.openLink = function(url) {
1621
+ if (!this._adapter) return Promise.reject(new Error('Not initialized'));
1622
+ return this._adapter.openLink(this._context, url);
1623
+ };
1624
+
1625
+ FrontMcpBridge.prototype.requestDisplayMode = function(mode) {
1626
+ if (!this._adapter) return Promise.reject(new Error('Not initialized'));
1627
+ var self = this;
1628
+ return this._adapter.requestDisplayMode(this._context, mode).then(function() {
1629
+ self._context.hostContext.displayMode = mode;
1630
+ });
1631
+ };
1632
+
1633
+ FrontMcpBridge.prototype.requestClose = function() {
1634
+ if (!this._adapter) return Promise.reject(new Error('Not initialized'));
1635
+ return this._adapter.requestClose(this._context);
1636
+ };
1637
+
1638
+ FrontMcpBridge.prototype.setWidgetState = function(state) {
1639
+ Object.assign(this._context.widgetState, state);
1640
+ this._saveWidgetState();
1641
+ };
1642
+
1643
+ FrontMcpBridge.prototype.onContextChange = function(callback) {
1644
+ var listeners = this._context.contextListeners;
1645
+ listeners.push(callback);
1646
+ return function() {
1647
+ var idx = listeners.indexOf(callback);
1648
+ if (idx !== -1) listeners.splice(idx, 1);
1649
+ };
1650
+ };
1651
+
1652
+ FrontMcpBridge.prototype.onToolResult = function(callback) {
1653
+ var listeners = this._context.toolResultListeners;
1654
+ listeners.push(callback);
1655
+ return function() {
1656
+ var idx = listeners.indexOf(callback);
1657
+ if (idx !== -1) listeners.splice(idx, 1);
1658
+ };
1659
+ };
1660
+
1661
+ // ==================== data-tool-call Click Handler ====================
1662
+
1663
+ FrontMcpBridge.prototype._setupDataToolCallHandler = function() {
1664
+ var self = this;
1665
+
1666
+ document.addEventListener('click', function(e) {
1667
+ // Find the closest element with data-tool-call attribute
1668
+ var target = e.target;
1669
+ while (target && target !== document) {
1670
+ if (target.hasAttribute && target.hasAttribute('data-tool-call')) {
1671
+ var toolName = target.getAttribute('data-tool-call');
1672
+ var argsAttr = target.getAttribute('data-tool-args');
1673
+ var args = {};
1674
+
1675
+ try {
1676
+ if (argsAttr) {
1677
+ args = JSON.parse(argsAttr);
1678
+ }
1679
+ } catch (parseErr) {
1680
+ console.error('[frontmcp] Failed to parse data-tool-args:', parseErr);
1681
+ }
1682
+
1683
+ log('data-tool-call clicked: ' + toolName);
1684
+
1685
+ // Show loading state - save original content first
1686
+ var originalContent = target.innerHTML;
1687
+ var originalDisabled = target.disabled;
1688
+ target.disabled = true;
1689
+ target.classList.add('opacity-50', 'cursor-not-allowed');
1690
+
1691
+ // Add spinner for buttons
1692
+ 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>';
1693
+ if (target.tagName === 'BUTTON') {
1694
+ target.innerHTML = spinner + 'Loading...';
1695
+ }
1696
+
1697
+ // Helper to reset button state
1698
+ function resetButton() {
1699
+ target.innerHTML = originalContent;
1700
+ target.disabled = originalDisabled;
1701
+ target.classList.remove('opacity-50', 'cursor-not-allowed');
1702
+ }
1703
+
1704
+ // Determine how to call the tool
1705
+ var toolCallPromise;
1706
+
1707
+ // Priority 1: Direct OpenAI SDK call (bypasses adapter abstraction)
1708
+ if (typeof window !== 'undefined' && window.openai && typeof window.openai.callTool === 'function') {
1709
+ log('Using OpenAI SDK directly for tool call');
1710
+ toolCallPromise = window.openai.callTool(toolName, args);
1711
+ }
1712
+ // Priority 2: Use adapter (if it supports tool calls)
1713
+ else if (self.hasCapability('canCallTools')) {
1714
+ log('Using adapter for tool call');
1715
+ toolCallPromise = self.callTool(toolName, args);
1716
+ }
1717
+ // No tool call capability
1718
+ else {
1719
+ console.error('[frontmcp] Tool calls not supported on this platform (' + self.adapterId + ')');
1720
+ resetButton();
1721
+ target.dispatchEvent(new CustomEvent('tool:error', {
1722
+ detail: { name: toolName, args: args, error: 'Tool calls not supported on this platform' },
1723
+ bubbles: true
1724
+ }));
1725
+ e.preventDefault();
1726
+ return;
1727
+ }
1728
+
1729
+ // Handle the tool call result
1730
+ toolCallPromise.then(function(result) {
1731
+ log('Tool call succeeded: ' + toolName);
1732
+ resetButton();
1733
+
1734
+ // Update bridge state to trigger widget re-render
1735
+ // React isn't hydrated in OpenAI iframe, so useState doesn't work
1736
+ // Instead, we use the bridge's reactive state system
1737
+ if (result && window.__frontmcp && window.__frontmcp.bridge && typeof window.__frontmcp.bridge.setWidgetState === 'function') {
1738
+ var newData = result.structuredContent || result;
1739
+ log('Updating bridge state with new data');
1740
+ window.__frontmcp.bridge.setWidgetState(newData);
1741
+ }
1742
+
1743
+ // Dispatch success event
1744
+ target.dispatchEvent(new CustomEvent('tool:success', {
1745
+ detail: { name: toolName, args: args, result: result },
1746
+ bubbles: true
1747
+ }));
1748
+ }).catch(function(err) {
1749
+ console.error('[frontmcp] Tool call failed: ' + toolName, err);
1750
+ resetButton();
1751
+ // Dispatch error event
1752
+ target.dispatchEvent(new CustomEvent('tool:error', {
1753
+ detail: { name: toolName, args: args, error: err.message || err },
1754
+ bubbles: true
1755
+ }));
1756
+ });
1757
+
1758
+ // Prevent default behavior (e.g., form submission)
1759
+ e.preventDefault();
1760
+ return;
1761
+ }
1762
+ target = target.parentElement;
1763
+ }
1764
+ }, true); // Use capture phase to handle before React handlers
1765
+ };
1766
+ `.trim();
1767
+ }
1768
+ function minifyJS(code) {
1769
+ return code.replace(/\/\*[\s\S]*?\*\//g, "").replace(/\/\/.*$/gm, "").replace(/\s+/g, " ").replace(/\s*([{};,:()[\]])\s*/g, "$1").replace(/;\}/g, "}").trim();
1770
+ }
1771
+ function generatePlatformBundle(platform, options = {}) {
1772
+ const platformAdapters = {
1773
+ chatgpt: ["openai", "generic"],
1774
+ claude: ["claude", "generic"],
1775
+ gemini: ["gemini", "generic"],
1776
+ universal: ["openai", "ext-apps", "claude", "gemini", "generic"]
1777
+ };
1778
+ return generateBridgeIIFE({
1779
+ ...options,
1780
+ adapters: platformAdapters[platform]
1781
+ });
1782
+ }
1783
+ var UNIVERSAL_BRIDGE_SCRIPT = generateBridgeIIFE();
1784
+ var BRIDGE_SCRIPT_TAGS = {
1785
+ universal: `<script>${UNIVERSAL_BRIDGE_SCRIPT}</script>`,
1786
+ chatgpt: `<script>${generatePlatformBundle("chatgpt")}</script>`,
1787
+ claude: `<script>${generatePlatformBundle("claude")}</script>`,
1788
+ gemini: `<script>${generatePlatformBundle("gemini")}</script>`
1789
+ };
1790
+
1791
+ // libs/uipack/src/runtime/mcp-bridge.ts
1792
+ var MCP_BRIDGE_RUNTIME = `
1793
+ <script>
1794
+ (function() {
1795
+ 'use strict';
1796
+
1797
+ // ==================== Environment Detection ====================
1798
+
1799
+ var isOpenAI = typeof window.openai !== 'undefined';
1800
+ var isExtApps = window.parent !== window && !isOpenAI;
1801
+ var isClaude = typeof window.claude !== 'undefined' ||
1802
+ (window.__mcpPlatform === 'claude');
1803
+ var isGemini = window.__mcpPlatform === 'gemini';
1804
+
1805
+ // ==================== Internal State ====================
1806
+
1807
+ var messageId = 0;
1808
+ var pendingRequests = new Map();
1809
+ var contextChangeListeners = [];
1810
+ var toolResultListeners = [];
1811
+
1812
+ // Default values for polyfilled properties
1813
+ var defaultSafeArea = { top: 0, bottom: 0, left: 0, right: 0 };
1814
+ var defaultUserAgent = { type: 'web', hover: true, touch: false };
1815
+
1816
+ // Host context (for ext-apps and polyfill mode)
1817
+ var hostContext = window.__mcpHostContext || {
1818
+ theme: 'light',
1819
+ displayMode: 'inline'
1820
+ };
1821
+
1822
+ // Trusted origin for postMessage validation (set during ext-apps initialization)
1823
+ var trustedOrigin = null;
1824
+
1825
+ // Detect device capabilities
1826
+ var detectUserAgent = function() {
1827
+ var ua = navigator.userAgent || '';
1828
+ var isMobile = /iPhone|iPad|iPod|Android/i.test(ua);
1829
+ var hasTouch = 'ontouchstart' in window || navigator.maxTouchPoints > 0;
1830
+ var hasHover = window.matchMedia && window.matchMedia('(hover: hover)').matches;
1831
+ return {
1832
+ type: isMobile ? 'mobile' : 'web',
1833
+ hover: hasHover !== false,
1834
+ touch: hasTouch
1835
+ };
1836
+ };
1837
+
1838
+ // Detect theme from system preference
1839
+ var detectTheme = function() {
1840
+ if (window.matchMedia && window.matchMedia('(prefers-color-scheme: dark)').matches) {
1841
+ return 'dark';
1842
+ }
1843
+ return 'light';
1844
+ };
1845
+
1846
+ // ==================== Bridge Implementation ====================
1847
+
1848
+ /**
1849
+ * MCP Bridge provides full OpenAI API compatibility.
1850
+ * On OpenAI, it proxies directly to window.openai.
1851
+ * On other platforms, it provides polyfills.
1852
+ */
1853
+ var bridge = {
1854
+ // ==================== Environment Info ====================
1855
+
1856
+ get provider() {
1857
+ if (isOpenAI) return 'openai';
1858
+ if (isClaude) return 'claude';
1859
+ if (isGemini) return 'gemini';
1860
+ if (isExtApps) return 'ext-apps';
1861
+ return 'unknown';
1862
+ },
1863
+
1864
+ // ==================== OpenAI-Compatible Properties ====================
1865
+
1866
+ get theme() {
1867
+ if (isOpenAI && window.openai) {
1868
+ return window.openai.theme || 'light';
1869
+ }
1870
+ return hostContext.theme || detectTheme();
1871
+ },
1872
+
1873
+ get userAgent() {
1874
+ if (isOpenAI && window.openai && window.openai.userAgent) {
1875
+ return window.openai.userAgent;
1876
+ }
1877
+ return hostContext.userAgent || detectUserAgent();
1878
+ },
1879
+
1880
+ get locale() {
1881
+ if (isOpenAI && window.openai && window.openai.locale) {
1882
+ return window.openai.locale;
1883
+ }
1884
+ return navigator.language || 'en-US';
1885
+ },
1886
+
1887
+ get maxHeight() {
1888
+ if (isOpenAI && window.openai) {
1889
+ return window.openai.maxHeight;
1890
+ }
1891
+ return hostContext.viewport ? hostContext.viewport.maxHeight : undefined;
1892
+ },
1893
+
1894
+ get displayMode() {
1895
+ if (isOpenAI && window.openai) {
1896
+ return window.openai.displayMode || 'inline';
1897
+ }
1898
+ return hostContext.displayMode || 'inline';
1899
+ },
1900
+
1901
+ get safeArea() {
1902
+ if (isOpenAI && window.openai && window.openai.safeArea) {
1903
+ return window.openai.safeArea;
1904
+ }
1905
+ return hostContext.safeArea || defaultSafeArea;
1906
+ },
1907
+
1908
+ get toolInput() {
1909
+ if (isOpenAI && window.openai && window.openai.toolInput) {
1910
+ return window.openai.toolInput;
1911
+ }
1912
+ return window.__mcpToolInput || {};
1913
+ },
1914
+
1915
+ get toolOutput() {
1916
+ if (isOpenAI && window.openai) {
1917
+ return window.openai.toolOutput;
1918
+ }
1919
+ return window.__mcpToolOutput;
1920
+ },
1921
+
1922
+ get structuredContent() {
1923
+ // Alias for toolOutput to maintain compatibility
1924
+ if (isOpenAI && window.openai) {
1925
+ return window.openai.toolOutput;
1926
+ }
1927
+ return window.__mcpStructuredContent;
1928
+ },
1929
+
1930
+ get toolResponseMetadata() {
1931
+ if (isOpenAI && window.openai && window.openai.toolResponseMetadata) {
1932
+ return window.openai.toolResponseMetadata;
1933
+ }
1934
+ return window.__mcpToolResponseMetadata || {};
1935
+ },
1936
+
1937
+ get widgetState() {
1938
+ if (isOpenAI && window.openai) {
1939
+ return window.openai.widgetState || {};
1940
+ }
1941
+ // Polyfill: use localStorage
1942
+ try {
1943
+ var stored = localStorage.getItem('__mcpWidgetState');
1944
+ return stored ? JSON.parse(stored) : {};
1945
+ } catch (e) {
1946
+ return {};
1947
+ }
1948
+ },
1949
+
1950
+ // ==================== OpenAI-Compatible Methods ====================
1951
+
1952
+ callTool: function(name, params) {
1953
+ if (isOpenAI && window.openai && window.openai.callTool) {
1954
+ return window.openai.callTool(name, params);
1955
+ }
1956
+ if (isClaude) {
1957
+ return Promise.reject(new Error(
1958
+ 'Tool calls are not supported in Claude widgets (network-blocked sandbox)'
1959
+ ));
1960
+ }
1961
+ if (isGemini) {
1962
+ return Promise.reject(new Error(
1963
+ 'Tool calls are not supported in Gemini widgets'
1964
+ ));
1965
+ }
1966
+ if (isExtApps) {
1967
+ return this._sendRequest('tools/call', { name: name, arguments: params });
1968
+ }
1969
+ return Promise.reject(new Error('Tool calls not supported in this environment'));
1970
+ },
1971
+
1972
+ requestDisplayMode: function(options) {
1973
+ if (isOpenAI && window.openai && window.openai.requestDisplayMode) {
1974
+ return window.openai.requestDisplayMode(options);
1975
+ }
1976
+ if (isExtApps) {
1977
+ return this._sendRequest('ui/request-display-mode', options);
1978
+ }
1979
+ // Polyfill: just update local state
1980
+ if (options && options.mode) {
1981
+ hostContext.displayMode = options.mode;
1982
+ contextChangeListeners.forEach(function(cb) {
1983
+ try { cb({ displayMode: options.mode }); } catch (e) {}
1984
+ });
1985
+ }
1986
+ return Promise.resolve();
1987
+ },
1988
+
1989
+ requestClose: function() {
1990
+ if (isOpenAI && window.openai && window.openai.requestClose) {
1991
+ return window.openai.requestClose();
1992
+ }
1993
+ if (isExtApps) {
1994
+ return this._sendRequest('ui/request-close', {});
1995
+ }
1996
+ // Polyfill: dispatch event for parent to handle
1997
+ window.dispatchEvent(new CustomEvent('mcp:request-close'));
1998
+ return Promise.resolve();
1999
+ },
2000
+
2001
+ openExternal: function(options) {
2002
+ var href = typeof options === 'string' ? options : (options && options.href);
2003
+ if (!href) {
2004
+ return Promise.reject(new Error('URL required'));
2005
+ }
2006
+ if (isOpenAI && window.openai && window.openai.openExternal) {
2007
+ return window.openai.openExternal({ href: href });
2008
+ }
2009
+ if (isExtApps) {
2010
+ return this._sendRequest('ui/open-link', { url: href });
2011
+ }
2012
+ // Fallback: open in new window
2013
+ window.open(href, '_blank', 'noopener,noreferrer');
2014
+ return Promise.resolve();
2015
+ },
2016
+
2017
+ // Alias for openExternal (backwards compatibility)
2018
+ openLink: function(url) {
2019
+ return this.openExternal({ href: url });
2020
+ },
2021
+
2022
+ sendFollowUpMessage: function(options) {
2023
+ var prompt = typeof options === 'string' ? options : (options && options.prompt);
2024
+ if (!prompt) {
2025
+ return Promise.reject(new Error('Prompt required'));
2026
+ }
2027
+ if (isOpenAI && window.openai && window.openai.sendFollowUpMessage) {
2028
+ return window.openai.sendFollowUpMessage({ prompt: prompt });
2029
+ }
2030
+ if (isClaude) {
2031
+ return Promise.reject(new Error(
2032
+ 'Follow-up messages are not supported in Claude widgets (network-blocked sandbox)'
2033
+ ));
2034
+ }
2035
+ if (isGemini) {
2036
+ return Promise.reject(new Error(
2037
+ 'Follow-up messages are not supported in Gemini widgets'
2038
+ ));
2039
+ }
2040
+ if (isExtApps) {
2041
+ return this._sendRequest('ui/message', {
2042
+ role: 'user',
2043
+ content: { type: 'text', text: prompt }
2044
+ });
2045
+ }
2046
+ return Promise.reject(new Error('Messages not supported in this environment'));
2047
+ },
2048
+
2049
+ // Alias for sendFollowUpMessage (backwards compatibility)
2050
+ sendMessage: function(content) {
2051
+ return this.sendFollowUpMessage({ prompt: content });
2052
+ },
2053
+
2054
+ setWidgetState: function(state) {
2055
+ if (isOpenAI && window.openai && window.openai.setWidgetState) {
2056
+ window.openai.setWidgetState(state);
2057
+ return;
2058
+ }
2059
+ // Polyfill: persist to localStorage
2060
+ try {
2061
+ localStorage.setItem('__mcpWidgetState', JSON.stringify(state));
2062
+ } catch (e) {
2063
+ console.warn('Failed to persist widget state:', e);
2064
+ }
2065
+ },
2066
+
2067
+ // ==================== Context API (MCP-specific) ====================
2068
+
2069
+ get context() {
2070
+ return {
2071
+ theme: this.theme,
2072
+ displayMode: this.displayMode,
2073
+ viewport: this.maxHeight ? { maxHeight: this.maxHeight } : undefined,
2074
+ userAgent: this.userAgent,
2075
+ locale: this.locale,
2076
+ safeArea: this.safeArea
2077
+ };
2078
+ },
2079
+
2080
+ onContextChange: function(callback) {
2081
+ contextChangeListeners.push(callback);
2082
+ return function() {
2083
+ var index = contextChangeListeners.indexOf(callback);
2084
+ if (index > -1) contextChangeListeners.splice(index, 1);
2085
+ };
2086
+ },
2087
+
2088
+ onToolResult: function(callback) {
2089
+ toolResultListeners.push(callback);
2090
+ return function() {
2091
+ var index = toolResultListeners.indexOf(callback);
2092
+ if (index > -1) toolResultListeners.splice(index, 1);
2093
+ };
2094
+ },
2095
+
2096
+ // ==================== Internal Methods ====================
2097
+
2098
+ _sendRequest: function(method, params) {
2099
+ return new Promise(function(resolve, reject) {
2100
+ var id = ++messageId;
2101
+ pendingRequests.set(id, { resolve: resolve, reject: reject });
2102
+
2103
+ window.parent.postMessage({
2104
+ jsonrpc: '2.0',
2105
+ id: id,
2106
+ method: method,
2107
+ params: params
2108
+ }, '*');
2109
+
2110
+ // Timeout after 30s
2111
+ setTimeout(function() {
2112
+ if (pendingRequests.has(id)) {
2113
+ pendingRequests.delete(id);
2114
+ reject(new Error('Request timeout'));
2115
+ }
2116
+ }, 30000);
2117
+ });
2118
+ },
2119
+
2120
+ _initExtApps: function() {
2121
+ var self = this;
2122
+ return this._sendRequest('ui/initialize', {}).then(function(result) {
2123
+ if (result && result.hostContext) {
2124
+ hostContext = Object.assign(hostContext, result.hostContext);
2125
+ }
2126
+ // Note: trustedOrigin is now set from first message event origin (trust-on-first-use)
2127
+ // Send initialized notification
2128
+ window.parent.postMessage({
2129
+ jsonrpc: '2.0',
2130
+ method: 'ui/notifications/initialized',
2131
+ params: {}
2132
+ }, '*');
2133
+ return result;
2134
+ });
2135
+ }
2136
+ };
2137
+
2138
+ // ==================== Event Handling ====================
2139
+
2140
+ window.addEventListener('message', function(event) {
2141
+ var data = event.data;
2142
+ if (!data || data.jsonrpc !== '2.0') return;
2143
+
2144
+ // Trust-on-first-use: Establish trusted origin from the first valid message
2145
+ if (isExtApps && !trustedOrigin && event.origin) {
2146
+ trustedOrigin = event.origin;
2147
+ }
2148
+
2149
+ // Validate origin for ext-apps environment to prevent spoofed messages
2150
+ if (isExtApps && trustedOrigin && event.origin !== trustedOrigin) {
2151
+ console.warn('MCP Bridge: Ignoring message from untrusted origin:', event.origin);
2152
+ return;
2153
+ }
2154
+
2155
+ // Handle responses
2156
+ if (data.id && pendingRequests.has(data.id)) {
2157
+ var pending = pendingRequests.get(data.id);
2158
+ pendingRequests.delete(data.id);
2159
+
2160
+ if (data.error) {
2161
+ var err = new Error(data.error.message || 'Unknown error');
2162
+ err.code = data.error.code;
2163
+ err.data = data.error.data;
2164
+ pending.reject(err);
2165
+ } else {
2166
+ pending.resolve(data.result);
2167
+ }
2168
+ return;
2169
+ }
2170
+
2171
+ // Handle notifications
2172
+ switch (data.method) {
2173
+ case 'ui/notifications/tool-input':
2174
+ window.__mcpToolInput = data.params && data.params.arguments;
2175
+ window.dispatchEvent(new CustomEvent('mcp:tool-input', { detail: data.params }));
2176
+ break;
2177
+
2178
+ case 'ui/notifications/tool-input-partial':
2179
+ if (data.params && data.params.arguments) {
2180
+ window.__mcpToolInput = Object.assign(window.__mcpToolInput || {}, data.params.arguments);
2181
+ }
2182
+ break;
2183
+
2184
+ case 'ui/notifications/tool-result':
2185
+ window.__mcpToolOutput = data.params && data.params.content;
2186
+ window.__mcpStructuredContent = data.params && data.params.structuredContent;
2187
+ toolResultListeners.forEach(function(cb) {
2188
+ try { cb(data.params); } catch (e) { console.error('Tool result listener error:', e); }
2189
+ });
2190
+ window.dispatchEvent(new CustomEvent('mcp:tool-result', { detail: data.params }));
2191
+ break;
2192
+
2193
+ case 'ui/notifications/tool-cancelled':
2194
+ window.dispatchEvent(new CustomEvent('mcp:tool-cancelled', { detail: data.params }));
2195
+ break;
2196
+
2197
+ case 'ui/host-context-change':
2198
+ hostContext = Object.assign(hostContext, data.params);
2199
+ contextChangeListeners.forEach(function(cb) {
2200
+ try { cb(data.params); } catch (e) { console.error('Context change listener error:', e); }
2201
+ });
2202
+ window.dispatchEvent(new CustomEvent('mcp:context-change', { detail: data.params }));
2203
+ break;
2204
+
2205
+ case 'ui/size-change':
2206
+ if (hostContext.viewport) {
2207
+ hostContext.viewport = Object.assign(hostContext.viewport, data.params);
2208
+ } else {
2209
+ hostContext.viewport = data.params;
2210
+ }
2211
+ break;
2212
+ }
2213
+ });
2214
+
2215
+ // ==================== Initialize ====================
2216
+
2217
+ // Export bridge
2218
+ window.mcpBridge = bridge;
2219
+
2220
+ // Also create window.openai polyfill for non-OpenAI platforms
2221
+ // This allows code written for OpenAI to work on other platforms
2222
+ if (!isOpenAI) {
2223
+ window.openai = {
2224
+ get theme() { return bridge.theme; },
2225
+ get userAgent() { return bridge.userAgent; },
2226
+ get locale() { return bridge.locale; },
2227
+ get maxHeight() { return bridge.maxHeight; },
2228
+ get displayMode() { return bridge.displayMode; },
2229
+ get safeArea() { return bridge.safeArea; },
2230
+ get toolInput() { return bridge.toolInput; },
2231
+ get toolOutput() { return bridge.toolOutput; },
2232
+ get toolResponseMetadata() { return bridge.toolResponseMetadata; },
2233
+ get widgetState() { return bridge.widgetState; },
2234
+ callTool: function(n, a) { return bridge.callTool(n, a); },
2235
+ requestDisplayMode: function(o) { return bridge.requestDisplayMode(o); },
2236
+ requestClose: function() { return bridge.requestClose(); },
2237
+ openExternal: function(o) { return bridge.openExternal(o); },
2238
+ sendFollowUpMessage: function(o) { return bridge.sendFollowUpMessage(o); },
2239
+ setWidgetState: function(s) { return bridge.setWidgetState(s); }
2240
+ };
2241
+ }
2242
+
2243
+ // Auto-initialize for ext-apps
2244
+ if (isExtApps) {
2245
+ bridge._initExtApps().catch(function(err) {
2246
+ console.warn('Failed to initialize MCP bridge:', err);
2247
+ });
2248
+ }
2249
+
2250
+ // Dispatch ready event
2251
+ window.dispatchEvent(new CustomEvent('mcp:bridge-ready'));
2252
+ })();
2253
+ </script>
2254
+ `;
2255
+ function getMCPBridgeScript() {
2256
+ const match = MCP_BRIDGE_RUNTIME.match(/<script>([\s\S]*?)<\/script>/);
2257
+ return match ? match[1].trim() : "";
2258
+ }
2259
+ function isMCPBridgeSupported() {
2260
+ if (typeof window === "undefined") return false;
2261
+ const w = window;
2262
+ return typeof w.openai !== "undefined" || typeof w.claude !== "undefined" || w.__mcpPlatform === "claude" || w.__mcpPlatform === "gemini" || w.__mcpPlatform === "ext-apps" || window.parent !== window;
2263
+ }
2264
+ var FRONTMCP_BRIDGE_RUNTIME = BRIDGE_SCRIPT_TAGS.universal;
2265
+ var PLATFORM_BRIDGE_SCRIPTS = BRIDGE_SCRIPT_TAGS;
2266
+
2267
+ // libs/uipack/src/runtime/csp.ts
2268
+ var DEFAULT_CDN_DOMAINS = [
2269
+ "https://cdn.jsdelivr.net",
2270
+ // Tailwind, Alpine, React, icons
2271
+ "https://cdnjs.cloudflare.com",
2272
+ // HTMX, other libraries
2273
+ "https://fonts.googleapis.com",
2274
+ // Google Fonts stylesheets
2275
+ "https://fonts.gstatic.com"
2276
+ // Google Fonts files
2277
+ ];
2278
+ var DEFAULT_CSP_DIRECTIVES = [
2279
+ "default-src 'none'",
2280
+ `script-src 'self' 'unsafe-inline' ${DEFAULT_CDN_DOMAINS.join(" ")}`,
2281
+ `style-src 'self' 'unsafe-inline' ${DEFAULT_CDN_DOMAINS.join(" ")}`,
2282
+ `img-src 'self' data: ${DEFAULT_CDN_DOMAINS.join(" ")}`,
2283
+ `font-src 'self' data: ${DEFAULT_CDN_DOMAINS.join(" ")}`,
2284
+ "connect-src 'none'"
2285
+ ];
2286
+ var RESTRICTIVE_CSP_DIRECTIVES = [
2287
+ "default-src 'none'",
2288
+ "script-src 'self' 'unsafe-inline'",
2289
+ "style-src 'self' 'unsafe-inline'",
2290
+ "img-src 'self' data:",
2291
+ "font-src 'self' data:",
2292
+ "connect-src 'none'"
2293
+ ];
2294
+ function buildCSPDirectives(csp) {
2295
+ if (!csp) {
2296
+ return [...DEFAULT_CSP_DIRECTIVES];
2297
+ }
2298
+ const validResourceDomains = sanitizeCSPDomains(csp.resourceDomains);
2299
+ const validConnectDomains = sanitizeCSPDomains(csp.connectDomains);
2300
+ const allResourceDomains = [.../* @__PURE__ */ new Set([...DEFAULT_CDN_DOMAINS, ...validResourceDomains])];
2301
+ const directives = [
2302
+ "default-src 'none'",
2303
+ `script-src 'self' 'unsafe-inline' ${allResourceDomains.join(" ")}`,
2304
+ `style-src 'self' 'unsafe-inline' ${allResourceDomains.join(" ")}`
2305
+ ];
2306
+ const imgSources = ["'self'", "data:", ...allResourceDomains];
2307
+ directives.push(`img-src ${imgSources.join(" ")}`);
2308
+ const fontSources = ["'self'", "data:", ...allResourceDomains];
2309
+ directives.push(`font-src ${fontSources.join(" ")}`);
2310
+ if (validConnectDomains.length) {
2311
+ directives.push(`connect-src ${validConnectDomains.join(" ")}`);
2312
+ } else {
2313
+ directives.push("connect-src 'none'");
2314
+ }
2315
+ return directives;
2316
+ }
2317
+ function buildCSPMetaTag(csp) {
2318
+ const directives = buildCSPDirectives(csp);
2319
+ const content = directives.join("; ");
2320
+ return `<meta http-equiv="Content-Security-Policy" content="${escapeAttribute(content)}">`;
2321
+ }
2322
+ function buildOpenAICSP(csp) {
2323
+ if (!csp) return void 0;
2324
+ const result = {};
2325
+ if (csp.connectDomains?.length) {
2326
+ result.connect_domains = csp.connectDomains;
2327
+ }
2328
+ if (csp.resourceDomains?.length) {
2329
+ result.resource_domains = csp.resourceDomains;
2330
+ }
2331
+ return Object.keys(result).length > 0 ? result : void 0;
2332
+ }
2333
+ function escapeAttribute(str) {
2334
+ return str.replace(/&/g, "&amp;").replace(/"/g, "&quot;").replace(/'/g, "&#39;").replace(/</g, "&lt;").replace(/>/g, "&gt;");
2335
+ }
2336
+ function validateCSPDomain(domain) {
2337
+ if (domain.startsWith("https://*.")) {
2338
+ const rest = domain.slice(10);
2339
+ return /^[a-zA-Z0-9]([a-zA-Z0-9-]*[a-zA-Z0-9])?\.[a-zA-Z]{2,}$/.test(rest);
2340
+ }
2341
+ try {
2342
+ const url = new URL(domain);
2343
+ return url.protocol === "https:";
2344
+ } catch {
2345
+ return false;
2346
+ }
2347
+ }
2348
+ function sanitizeCSPDomains(domains) {
2349
+ if (!domains) return [];
2350
+ const valid = [];
2351
+ for (const domain of domains) {
2352
+ if (validateCSPDomain(domain)) {
2353
+ valid.push(domain);
2354
+ } else {
2355
+ console.warn(`Invalid CSP domain ignored: ${domain}`);
2356
+ }
2357
+ }
2358
+ return valid;
2359
+ }
2360
+
2361
+ // libs/uipack/src/theme/cdn.ts
2362
+ var CDN = {
2363
+ /**
2364
+ * Tailwind CSS v4 Browser CDN
2365
+ * Generates styles on-the-fly with @theme support
2366
+ * @see https://tailwindcss.com/docs/installation/play-cdn
2367
+ */
2368
+ tailwind: "https://cdn.jsdelivr.net/npm/@tailwindcss/browser@4",
2369
+ /**
2370
+ * HTMX 2.x - High power tools for HTML
2371
+ * Enables AJAX, WebSockets, Server Sent Events directly in HTML
2372
+ * @see https://htmx.org
2373
+ */
2374
+ htmx: {
2375
+ url: "https://cdnjs.cloudflare.com/ajax/libs/htmx/2.0.7/htmx.min.js",
2376
+ integrity: "sha512-T6VLg/MJYMbLTmQ8VLvonbWg8VOvmDhXcOvHzCwo6ShdGuUU5SEcp1IAPXL4k9lVoMi8gRXl5K/S/zh43Y9rJA=="
2377
+ },
2378
+ /**
2379
+ * Alpine.js - Lightweight reactive framework
2380
+ * Used for more complex client-side interactions
2381
+ * @see https://alpinejs.dev
2382
+ */
2383
+ alpine: {
2384
+ url: "https://cdn.jsdelivr.net/npm/alpinejs@3.14.3/dist/cdn.min.js",
2385
+ integrity: "sha384-6zY8MFQJ/EqS1r4RJl+7j8rvZPuBWpT0RzWf+IFcKhxqUzQNmJzA1X1VEVZhYaEz"
2386
+ },
2387
+ /**
2388
+ * Google Fonts - Inter for modern UI typography
2389
+ */
2390
+ fonts: {
2391
+ preconnect: ["https://fonts.googleapis.com", "https://fonts.gstatic.com"],
2392
+ inter: "https://fonts.googleapis.com/css2?family=Inter:wght@400;500;600;700&display=swap",
2393
+ mono: "https://fonts.googleapis.com/css2?family=JetBrains+Mono:wght@400;500;600&display=swap"
2394
+ },
2395
+ /**
2396
+ * Lucide Icons - Beautiful & consistent icons
2397
+ * @see https://lucide.dev
2398
+ */
2399
+ icons: {
2400
+ url: "https://cdn.jsdelivr.net/npm/lucide@0.294.0/dist/umd/lucide.min.js",
2401
+ integrity: "sha384-wpLmHb7v7V1LsEuTmPQ9tXqWZvTtRWWVqJuE+Yz6X0I6O2T6bHJVeXH1lVWqF4qE"
2402
+ }
2403
+ };
2404
+ var scriptCache = /* @__PURE__ */ new Map();
2405
+ function getCachedScript(url) {
2406
+ return scriptCache.get(url);
2407
+ }
2408
+ function isScriptCached(url) {
2409
+ return scriptCache.has(url);
2410
+ }
2411
+ function buildFontPreconnect() {
2412
+ return CDN.fonts.preconnect.map((url, i) => `<link rel="preconnect" href="${url}"${i > 0 ? " crossorigin" : ""}>`).join("\n ");
2413
+ }
2414
+ function buildFontStylesheets(options = {}) {
2415
+ const { inter = true, mono = false } = options;
2416
+ const links = [];
2417
+ if (inter) {
2418
+ links.push(`<link href="${CDN.fonts.inter}" rel="stylesheet">`);
2419
+ }
2420
+ if (mono) {
2421
+ links.push(`<link href="${CDN.fonts.mono}" rel="stylesheet">`);
2422
+ }
2423
+ return links.join("\n ");
2424
+ }
2425
+ function buildScriptTag(url, integrity, options = {}) {
2426
+ const attrs = [`src="${url}"`];
2427
+ if (integrity) {
2428
+ attrs.push(`integrity="${integrity}"`);
2429
+ attrs.push('crossorigin="anonymous"');
2430
+ }
2431
+ if (options.defer) attrs.push("defer");
2432
+ if (options.async) attrs.push("async");
2433
+ return `<script ${attrs.join(" ")}></script>`;
2434
+ }
2435
+ function buildInlineScriptTag(content) {
2436
+ return `<script>${content}</script>`;
2437
+ }
2438
+ function buildCdnScripts(options = {}) {
2439
+ const { tailwind = true, htmx = true, alpine = false, icons = false, inline = false } = options;
2440
+ const scripts = [];
2441
+ if (inline) {
2442
+ if (tailwind) {
2443
+ if (isScriptCached(CDN.tailwind)) {
2444
+ scripts.push(buildInlineScriptTag(getCachedScript(CDN.tailwind)));
2445
+ } else {
2446
+ console.warn(
2447
+ "[frontmcp/ui] Inline mode requested but Tailwind script not cached. Call fetchAndCacheScripts() first."
2448
+ );
2449
+ }
2450
+ }
2451
+ if (htmx) {
2452
+ if (isScriptCached(CDN.htmx.url)) {
2453
+ scripts.push(buildInlineScriptTag(getCachedScript(CDN.htmx.url)));
2454
+ } else {
2455
+ console.warn(
2456
+ "[frontmcp/ui] Inline mode requested but HTMX script not cached. Call fetchAndCacheScripts() first."
2457
+ );
2458
+ }
2459
+ }
2460
+ if (alpine) {
2461
+ if (isScriptCached(CDN.alpine.url)) {
2462
+ scripts.push(buildInlineScriptTag(getCachedScript(CDN.alpine.url)));
2463
+ } else {
2464
+ console.warn(
2465
+ "[frontmcp/ui] Inline mode requested but Alpine.js script not cached. Call fetchAndCacheScripts() first."
2466
+ );
2467
+ }
2468
+ }
2469
+ if (icons) {
2470
+ if (isScriptCached(CDN.icons.url)) {
2471
+ scripts.push(buildInlineScriptTag(getCachedScript(CDN.icons.url)));
2472
+ } else {
2473
+ console.warn(
2474
+ "[frontmcp/ui] Inline mode requested but Lucide icons script not cached. Call fetchAndCacheScripts() first."
2475
+ );
2476
+ }
2477
+ }
2478
+ } else {
2479
+ if (tailwind) {
2480
+ scripts.push(buildScriptTag(CDN.tailwind));
2481
+ }
2482
+ if (htmx) {
2483
+ scripts.push(buildScriptTag(CDN.htmx.url, CDN.htmx.integrity));
2484
+ }
2485
+ if (alpine) {
2486
+ scripts.push(buildScriptTag(CDN.alpine.url, CDN.alpine.integrity, { defer: true }));
2487
+ }
2488
+ if (icons) {
2489
+ scripts.push(buildScriptTag(CDN.icons.url, CDN.icons.integrity));
2490
+ }
2491
+ }
2492
+ return scripts.join("\n ");
2493
+ }
2494
+
2495
+ // libs/uipack/src/theme/platforms.ts
2496
+ var OPENAI_PLATFORM = {
2497
+ id: "openai",
2498
+ name: "OpenAI",
2499
+ supportsWidgets: true,
2500
+ supportsTailwind: true,
2501
+ supportsHtmx: true,
2502
+ networkMode: "full",
2503
+ scriptStrategy: "cdn",
2504
+ options: {
2505
+ sdk: "apps-sdk",
2506
+ version: "1.0"
2507
+ }
2508
+ };
2509
+ var CLAUDE_PLATFORM = {
2510
+ id: "claude",
2511
+ name: "Claude (Artifacts)",
2512
+ supportsWidgets: true,
2513
+ supportsTailwind: true,
2514
+ supportsHtmx: false,
2515
+ // Network blocked, HTMX won't work for API calls
2516
+ networkMode: "blocked",
2517
+ scriptStrategy: "inline",
2518
+ maxInlineSize: 100 * 1024,
2519
+ // 100KB limit for artifacts
2520
+ cspRestrictions: ["script-src 'unsafe-inline'", "connect-src 'none'"],
2521
+ options: {
2522
+ mode: "artifacts",
2523
+ framework: "react"
2524
+ // Claude artifacts prefer React
2525
+ }
2526
+ };
2527
+ function canUseCdn(platform) {
2528
+ return platform.networkMode === "full" && platform.scriptStrategy === "cdn";
2529
+ }
2530
+ function needsInlineScripts(platform) {
2531
+ return platform.scriptStrategy === "inline" || platform.networkMode === "blocked";
2532
+ }
2533
+
2534
+ // libs/uipack/src/theme/presets/github-openai.ts
2535
+ var GITHUB_OPENAI_THEME = {
2536
+ name: "github-openai",
2537
+ colors: {
2538
+ semantic: {
2539
+ // Primary: Near-black for main actions and branding
2540
+ primary: "#24292f",
2541
+ // Secondary: Medium gray for secondary elements
2542
+ secondary: "#57606a",
2543
+ // Accent: Blue for links, focus states, and highlights
2544
+ accent: "#0969da",
2545
+ // Status colors
2546
+ success: "#1a7f37",
2547
+ // GitHub green
2548
+ warning: "#9a6700",
2549
+ // Amber warning
2550
+ danger: "#cf222e",
2551
+ // GitHub red
2552
+ info: "#0969da"
2553
+ // Blue info
2554
+ },
2555
+ surface: {
2556
+ // Pure white background
2557
+ background: "#ffffff",
2558
+ // Light gray surface (GitHub code background style)
2559
+ surface: "#f6f8fa",
2560
+ // White elevated surfaces (modals, cards)
2561
+ elevated: "#ffffff",
2562
+ // Dark semi-transparent overlay
2563
+ overlay: "rgba(27, 31, 36, 0.5)"
2564
+ },
2565
+ text: {
2566
+ // Near-black for primary text
2567
+ primary: "#24292f",
2568
+ // Gray for secondary/muted text
2569
+ secondary: "#57606a",
2570
+ // Light gray for disabled text
2571
+ disabled: "#8c959f",
2572
+ // White for text on dark backgrounds
2573
+ inverse: "#ffffff",
2574
+ // Blue for links
2575
+ link: "#0969da"
2576
+ },
2577
+ border: {
2578
+ // Light gray border (GitHub style)
2579
+ default: "#d0d7de",
2580
+ // Medium gray on hover
2581
+ hover: "#8c959f",
2582
+ // Blue focus ring
2583
+ focus: "#0969da",
2584
+ // Subtle divider
2585
+ divider: "#d8dee4"
2586
+ }
2587
+ },
2588
+ typography: {
2589
+ families: {
2590
+ // System UI font stack (GitHub/Apple style)
2591
+ sans: '-apple-system, BlinkMacSystemFont, "Segoe UI", "Noto Sans", Helvetica, Arial, sans-serif, "Apple Color Emoji", "Segoe UI Emoji"',
2592
+ // Monospace stack
2593
+ mono: 'ui-monospace, SFMono-Regular, SF Mono, Menlo, Consolas, "Liberation Mono", monospace'
2594
+ },
2595
+ sizes: {
2596
+ xs: "0.75rem",
2597
+ // 12px
2598
+ sm: "0.875rem",
2599
+ // 14px
2600
+ base: "1rem",
2601
+ // 16px
2602
+ lg: "1.125rem",
2603
+ // 18px
2604
+ xl: "1.25rem",
2605
+ // 20px
2606
+ "2xl": "1.5rem",
2607
+ // 24px
2608
+ "3xl": "1.875rem",
2609
+ // 30px
2610
+ "4xl": "2.25rem"
2611
+ // 36px
2612
+ },
2613
+ weights: {
2614
+ normal: "400",
2615
+ medium: "500",
2616
+ semibold: "600",
2617
+ bold: "700"
2618
+ }
2619
+ },
2620
+ radius: {
2621
+ none: "0",
2622
+ sm: "3px",
2623
+ // GitHub uses smaller radii
2624
+ md: "6px",
2625
+ lg: "8px",
2626
+ xl: "12px",
2627
+ "2xl": "16px",
2628
+ full: "9999px"
2629
+ },
2630
+ shadows: {
2631
+ // Subtle shadows with gray tones
2632
+ sm: "0 1px 0 rgba(27, 31, 36, 0.04)",
2633
+ md: "0 3px 6px rgba(140, 149, 159, 0.15)",
2634
+ lg: "0 8px 24px rgba(140, 149, 159, 0.2)",
2635
+ xl: "0 12px 28px rgba(140, 149, 159, 0.3)"
2636
+ },
2637
+ components: {
2638
+ button: {
2639
+ radius: "6px",
2640
+ paddingX: "16px",
2641
+ paddingY: "5px",
2642
+ fontSize: "14px",
2643
+ fontWeight: "500"
2644
+ },
2645
+ card: {
2646
+ radius: "6px",
2647
+ padding: "16px",
2648
+ shadow: "0 1px 0 rgba(27, 31, 36, 0.04)",
2649
+ borderWidth: "1px"
2650
+ },
2651
+ input: {
2652
+ radius: "6px",
2653
+ paddingX: "12px",
2654
+ paddingY: "5px",
2655
+ borderWidth: "1px",
2656
+ focusRingWidth: "3px"
2657
+ }
2658
+ },
2659
+ cdn: {
2660
+ fonts: {
2661
+ preconnect: ["https://fonts.googleapis.com", "https://fonts.gstatic.com"],
2662
+ stylesheets: [
2663
+ // System UI fonts don't need external stylesheets, but we include
2664
+ // Inter as an optional enhancement for consistent cross-platform rendering
2665
+ "https://fonts.googleapis.com/css2?family=Inter:wght@400;500;600;700&display=swap"
2666
+ ]
2667
+ },
2668
+ icons: {
2669
+ script: {
2670
+ url: "https://cdn.jsdelivr.net/npm/lucide@0.294.0/dist/umd/lucide.min.js"
2671
+ }
2672
+ },
2673
+ scripts: {
2674
+ tailwind: "https://cdn.jsdelivr.net/npm/@tailwindcss/browser@4",
2675
+ htmx: {
2676
+ url: "https://cdnjs.cloudflare.com/ajax/libs/htmx/2.0.7/htmx.min.js",
2677
+ integrity: "sha512-T6VLg/MJYMbLTmQ8VLvonbWg8VOvmDhXcOvHzCwo6ShdGuUU5SEcp1IAPXL4k9lVoMi8gRXl5K/S/zh43Y9rJA=="
2678
+ },
2679
+ alpine: {
2680
+ url: "https://cdn.jsdelivr.net/npm/alpinejs@3.14.3/dist/cdn.min.js",
2681
+ integrity: "sha384-6zY8MFQJ/EqS1r4RJl+7j8rvZPuBWpT0RzWf+IFcKhxqUzQNmJzA1X1VEVZhYaEz"
2682
+ }
2683
+ }
2684
+ }
2685
+ };
2686
+ var DEFAULT_THEME = GITHUB_OPENAI_THEME;
2687
+
2688
+ // libs/uipack/src/theme/theme.ts
2689
+ function mergeThemesCore(base, override) {
2690
+ const baseColors = base.colors ?? { semantic: { primary: "#24292f" } };
2691
+ const filterStrings = (arr) => arr.filter((s) => s !== void 0);
2692
+ return {
2693
+ ...base,
2694
+ ...override,
2695
+ colors: {
2696
+ ...baseColors,
2697
+ ...override.colors,
2698
+ semantic: { ...baseColors.semantic, ...override.colors?.semantic },
2699
+ surface: { ...baseColors.surface, ...override.colors?.surface },
2700
+ text: { ...baseColors.text, ...override.colors?.text },
2701
+ border: { ...baseColors.border, ...override.colors?.border },
2702
+ custom: { ...baseColors.custom, ...override.colors?.custom }
2703
+ },
2704
+ typography: {
2705
+ ...base.typography,
2706
+ ...override.typography,
2707
+ families: { ...base.typography?.families, ...override.typography?.families },
2708
+ sizes: { ...base.typography?.sizes, ...override.typography?.sizes },
2709
+ weights: { ...base.typography?.weights, ...override.typography?.weights },
2710
+ lineHeight: { ...base.typography?.lineHeight, ...override.typography?.lineHeight }
2711
+ },
2712
+ spacing: { ...base.spacing, ...override.spacing },
2713
+ radius: { ...base.radius, ...override.radius },
2714
+ shadows: { ...base.shadows, ...override.shadows },
2715
+ components: {
2716
+ ...base.components,
2717
+ ...override.components,
2718
+ button: { ...base.components?.button, ...override.components?.button },
2719
+ card: { ...base.components?.card, ...override.components?.card },
2720
+ input: { ...base.components?.input, ...override.components?.input }
2721
+ },
2722
+ cdn: {
2723
+ ...base.cdn,
2724
+ ...override.cdn,
2725
+ fonts: {
2726
+ // Concatenate then dedupe so base entries are preserved without duplicates
2727
+ preconnect: filterStrings(
2728
+ Array.from(/* @__PURE__ */ new Set([...base.cdn?.fonts?.preconnect ?? [], ...override.cdn?.fonts?.preconnect ?? []]))
2729
+ ),
2730
+ stylesheets: filterStrings(
2731
+ Array.from(/* @__PURE__ */ new Set([...base.cdn?.fonts?.stylesheets ?? [], ...override.cdn?.fonts?.stylesheets ?? []]))
2732
+ )
2733
+ },
2734
+ icons: {
2735
+ ...base.cdn?.icons,
2736
+ ...override.cdn?.icons,
2737
+ // Deep merge script to preserve integrity when only url is overridden
2738
+ script: override.cdn?.icons?.script ? { ...base.cdn?.icons?.script, ...override.cdn?.icons?.script } : base.cdn?.icons?.script
2739
+ },
2740
+ scripts: {
2741
+ // tailwind is a simple string, just use override or base
2742
+ tailwind: override.cdn?.scripts?.tailwind ?? base.cdn?.scripts?.tailwind,
2743
+ // Deep merge htmx/alpine to preserve integrity when only url is overridden
2744
+ htmx: override.cdn?.scripts?.htmx ? { ...base.cdn?.scripts?.htmx, ...override.cdn?.scripts?.htmx } : base.cdn?.scripts?.htmx,
2745
+ alpine: override.cdn?.scripts?.alpine ? { ...base.cdn?.scripts?.alpine, ...override.cdn?.scripts?.alpine } : base.cdn?.scripts?.alpine
2746
+ }
2747
+ },
2748
+ customVars: { ...base.customVars, ...override.customVars },
2749
+ customCss: [base.customCss, override.customCss].filter(Boolean).join("\n")
2750
+ };
2751
+ }
2752
+ function mergeThemes(base, override) {
2753
+ const merged = mergeThemesCore(base, override);
2754
+ let darkVariant;
2755
+ if (override.dark !== void 0) {
2756
+ const darkBase = base.dark ?? base;
2757
+ const { dark: _nestedDark, ...overrideDarkWithoutNested } = override.dark;
2758
+ darkVariant = mergeThemesCore(darkBase, overrideDarkWithoutNested);
2759
+ } else if (base.dark !== void 0) {
2760
+ const { dark: _nestedDark, ...baseDarkWithoutNested } = base.dark;
2761
+ darkVariant = baseDarkWithoutNested;
2762
+ }
2763
+ return {
2764
+ ...merged,
2765
+ dark: darkVariant
2766
+ };
2767
+ }
2768
+ function emitColorScale(lines, name, scale) {
2769
+ for (const [shade, value] of Object.entries(scale)) {
2770
+ if (value) lines.push(`--color-${name}-${shade}: ${value};`);
2771
+ }
2772
+ }
2773
+ function buildThemeCss(theme) {
2774
+ const lines = [];
2775
+ const semantic = theme.colors.semantic;
2776
+ if (typeof semantic.primary === "string") {
2777
+ lines.push(`--color-primary: ${semantic.primary};`);
2778
+ } else if (semantic.primary) {
2779
+ emitColorScale(lines, "primary", semantic.primary);
2780
+ }
2781
+ if (semantic.secondary) {
2782
+ if (typeof semantic.secondary === "string") {
2783
+ lines.push(`--color-secondary: ${semantic.secondary};`);
2784
+ } else {
2785
+ emitColorScale(lines, "secondary", semantic.secondary);
2786
+ }
2787
+ }
2788
+ if (semantic.accent) {
2789
+ if (typeof semantic.accent === "string") {
2790
+ lines.push(`--color-accent: ${semantic.accent};`);
2791
+ } else {
2792
+ emitColorScale(lines, "accent", semantic.accent);
2793
+ }
2794
+ }
2795
+ if (semantic.neutral) {
2796
+ if (typeof semantic.neutral === "string") {
2797
+ lines.push(`--color-neutral: ${semantic.neutral};`);
2798
+ } else {
2799
+ emitColorScale(lines, "neutral", semantic.neutral);
2800
+ }
2801
+ }
2802
+ if (semantic.success) lines.push(`--color-success: ${semantic.success};`);
2803
+ if (semantic.warning) lines.push(`--color-warning: ${semantic.warning};`);
2804
+ if (semantic.danger) lines.push(`--color-danger: ${semantic.danger};`);
2805
+ if (semantic.info) lines.push(`--color-info: ${semantic.info};`);
2806
+ const surface = theme.colors.surface;
2807
+ if (surface?.background) lines.push(`--color-background: ${surface.background};`);
2808
+ if (surface?.surface) lines.push(`--color-surface: ${surface.surface};`);
2809
+ if (surface?.elevated) lines.push(`--color-elevated: ${surface.elevated};`);
2810
+ if (surface?.overlay) lines.push(`--color-overlay: ${surface.overlay};`);
2811
+ const text = theme.colors.text;
2812
+ if (text?.primary) lines.push(`--color-text-primary: ${text.primary};`);
2813
+ if (text?.secondary) lines.push(`--color-text-secondary: ${text.secondary};`);
2814
+ if (text?.disabled) lines.push(`--color-text-disabled: ${text.disabled};`);
2815
+ if (text?.inverse) lines.push(`--color-text-inverse: ${text.inverse};`);
2816
+ if (text?.link) lines.push(`--color-text-link: ${text.link};`);
2817
+ const border = theme.colors.border;
2818
+ if (border?.default) lines.push(`--color-border: ${border.default};`);
2819
+ if (border?.hover) lines.push(`--color-border-hover: ${border.hover};`);
2820
+ if (border?.focus) lines.push(`--color-border-focus: ${border.focus};`);
2821
+ if (border?.divider) lines.push(`--color-divider: ${border.divider};`);
2822
+ if (theme.colors.custom) {
2823
+ for (const [key, value] of Object.entries(theme.colors.custom)) {
2824
+ lines.push(`--color-${key}: ${value};`);
2825
+ }
2826
+ }
2827
+ const typography = theme.typography;
2828
+ if (typography?.families?.sans) lines.push(`--font-sans: ${typography.families.sans};`);
2829
+ if (typography?.families?.serif) lines.push(`--font-serif: ${typography.families.serif};`);
2830
+ if (typography?.families?.mono) lines.push(`--font-mono: ${typography.families.mono};`);
2831
+ if (typography?.families?.display) lines.push(`--font-display: ${typography.families.display};`);
2832
+ const radius = theme.radius;
2833
+ if (radius?.none) lines.push(`--radius-none: ${radius.none};`);
2834
+ if (radius?.sm) lines.push(`--radius-sm: ${radius.sm};`);
2835
+ if (radius?.md) lines.push(`--radius-md: ${radius.md};`);
2836
+ if (radius?.lg) lines.push(`--radius-lg: ${radius.lg};`);
2837
+ if (radius?.xl) lines.push(`--radius-xl: ${radius.xl};`);
2838
+ if (radius?.["2xl"]) lines.push(`--radius-2xl: ${radius["2xl"]};`);
2839
+ if (radius?.full) lines.push(`--radius-full: ${radius.full};`);
2840
+ if (theme.customVars) {
2841
+ for (const [key, value] of Object.entries(theme.customVars)) {
2842
+ lines.push(`${key}: ${value};`);
2843
+ }
2844
+ }
2845
+ return lines.join("\n ");
2846
+ }
2847
+
2848
+ // libs/uipack/src/runtime/wrapper.ts
2849
+ init_utils();
2850
+
2851
+ // libs/uipack/src/runtime/sanitizer.ts
2852
+ var REDACTION_TOKENS = {
2853
+ EMAIL: "[EMAIL]",
2854
+ PHONE: "[PHONE]",
2855
+ CARD: "[CARD]",
2856
+ ID: "[ID]",
2857
+ IP: "[IP]",
2858
+ REDACTED: "[REDACTED]"
2859
+ };
2860
+ var PII_PATTERNS = {
2861
+ // Email: user@domain.tld
2862
+ email: /^[a-zA-Z0-9._%+-]+@[a-zA-Z0-9.-]+\.[a-zA-Z]{2,}$/,
2863
+ // Email embedded in text
2864
+ emailInText: /[a-zA-Z0-9._%+-]+@[a-zA-Z0-9.-]+\.[a-zA-Z]{2,}/g,
2865
+ // Phone: Various formats (US-centric but flexible)
2866
+ phone: /^[+]?[(]?[0-9]{1,4}[)]?[-\s.]?[0-9]{1,4}[-\s.]?[0-9]{1,9}$/,
2867
+ // Phone embedded in text
2868
+ phoneInText: /(?:\+?1[-.\s]?)?\(?[0-9]{3}\)?[-.\s]?[0-9]{3}[-.\s]?[0-9]{4}/g,
2869
+ // Credit card: 13-19 digits (with optional separators)
2870
+ creditCard: /^(?:[0-9]{4}[-\s]?){3,4}[0-9]{1,4}$/,
2871
+ // Credit card embedded in text
2872
+ creditCardInText: /\b(?:[0-9]{4}[-\s]?){3,4}[0-9]{1,4}\b/g,
2873
+ // SSN: XXX-XX-XXXX
2874
+ ssn: /^[0-9]{3}[-]?[0-9]{2}[-]?[0-9]{4}$/,
2875
+ // SSN embedded in text
2876
+ ssnInText: /\b[0-9]{3}[-]?[0-9]{2}[-]?[0-9]{4}\b/g,
2877
+ // IPv4 address
2878
+ ipv4: /^(?:(?:25[0-5]|2[0-4][0-9]|[01]?[0-9][0-9]?)\.){3}(?:25[0-5]|2[0-4][0-9]|[01]?[0-9][0-9]?)$/,
2879
+ // IPv4 embedded in text
2880
+ ipv4InText: /\b(?:(?:25[0-5]|2[0-4][0-9]|[01]?[0-9][0-9]?)\.){3}(?:25[0-5]|2[0-4][0-9]|[01]?[0-9][0-9]?)\b/g
2881
+ };
2882
+ function isEmail(value) {
2883
+ return PII_PATTERNS.email.test(value);
2884
+ }
2885
+ function isPhone(value) {
2886
+ return PII_PATTERNS.phone.test(value);
2887
+ }
2888
+ function isCreditCard(value) {
2889
+ const digits = value.replace(/[-\s]/g, "");
2890
+ return digits.length >= 13 && digits.length <= 19 && PII_PATTERNS.creditCard.test(value);
2891
+ }
2892
+ function isSSN(value) {
2893
+ return PII_PATTERNS.ssn.test(value);
2894
+ }
2895
+ function isIPv4(value) {
2896
+ return PII_PATTERNS.ipv4.test(value);
2897
+ }
2898
+ function detectPIIType(value) {
2899
+ if (isEmail(value)) return "EMAIL";
2900
+ if (isCreditCard(value)) return "CARD";
2901
+ if (isSSN(value)) return "ID";
2902
+ if (isPhone(value)) return "PHONE";
2903
+ if (isIPv4(value)) return "IP";
2904
+ return null;
2905
+ }
2906
+ function redactPIIFromText(text) {
2907
+ let result = text;
2908
+ result = result.replace(PII_PATTERNS.creditCardInText, REDACTION_TOKENS.CARD);
2909
+ result = result.replace(PII_PATTERNS.ssnInText, REDACTION_TOKENS.ID);
2910
+ result = result.replace(PII_PATTERNS.emailInText, REDACTION_TOKENS.EMAIL);
2911
+ result = result.replace(PII_PATTERNS.phoneInText, REDACTION_TOKENS.PHONE);
2912
+ result = result.replace(PII_PATTERNS.ipv4InText, REDACTION_TOKENS.IP);
2913
+ return result;
2914
+ }
2915
+ function sanitizeValue(key, value, path, options, depth, visited) {
2916
+ const maxDepth = options.maxDepth ?? 10;
2917
+ if (depth > maxDepth) {
2918
+ return value;
2919
+ }
2920
+ if (value === null || value === void 0) {
2921
+ return value;
2922
+ }
2923
+ if (typeof options.mode === "function") {
2924
+ return options.mode(key, value, path);
2925
+ }
2926
+ if (Array.isArray(options.mode)) {
2927
+ const lowerKey = key.toLowerCase();
2928
+ if (options.mode.some((f) => f.toLowerCase() === lowerKey)) {
2929
+ return REDACTION_TOKENS.REDACTED;
2930
+ }
2931
+ }
2932
+ if (typeof value === "string") {
2933
+ if (options.mode === true) {
2934
+ const piiType = detectPIIType(value);
2935
+ if (piiType) {
2936
+ return REDACTION_TOKENS[piiType];
2937
+ }
2938
+ if (options.sanitizeInText !== false) {
2939
+ const redacted = redactPIIFromText(value);
2940
+ if (redacted !== value) {
2941
+ return redacted;
2942
+ }
2943
+ }
2944
+ }
2945
+ return value;
2946
+ }
2947
+ if (Array.isArray(value)) {
2948
+ if (visited.has(value)) {
2949
+ return REDACTION_TOKENS.REDACTED;
2950
+ }
2951
+ visited.add(value);
2952
+ return value.map(
2953
+ (item, index) => sanitizeValue(String(index), item, [...path, String(index)], options, depth + 1, visited)
2954
+ );
2955
+ }
2956
+ if (typeof value === "object") {
2957
+ if (visited.has(value)) {
2958
+ return REDACTION_TOKENS.REDACTED;
2959
+ }
2960
+ visited.add(value);
2961
+ const result = {};
2962
+ for (const [k, v] of Object.entries(value)) {
2963
+ result[k] = sanitizeValue(k, v, [...path, k], options, depth + 1, visited);
2964
+ }
2965
+ return result;
2966
+ }
2967
+ return value;
2968
+ }
2969
+ function sanitizeInput(input, mode = true) {
2970
+ if (!input || typeof input !== "object") {
2971
+ return {};
2972
+ }
2973
+ const options = {
2974
+ mode,
2975
+ maxDepth: 10,
2976
+ sanitizeInText: true
2977
+ };
2978
+ const visited = /* @__PURE__ */ new WeakSet();
2979
+ return sanitizeValue("", input, [], options, 0, visited);
2980
+ }
2981
+ function createSanitizer(mode = true) {
2982
+ return (input) => sanitizeInput(input, mode);
2983
+ }
2984
+ function detectPII(input, options) {
2985
+ const maxDepth = options?.maxDepth ?? 10;
2986
+ const fields = [];
2987
+ const visited = /* @__PURE__ */ new WeakSet();
2988
+ const check = (value, path, depth) => {
2989
+ if (depth > maxDepth) return;
2990
+ if (value === null || value === void 0) return;
2991
+ if (typeof value === "string") {
2992
+ if (detectPIIType(value)) {
2993
+ fields.push(path.join("."));
2994
+ }
2995
+ return;
2996
+ }
2997
+ if (Array.isArray(value)) {
2998
+ if (visited.has(value)) return;
2999
+ visited.add(value);
3000
+ value.forEach((item, index) => check(item, [...path, String(index)], depth + 1));
3001
+ return;
3002
+ }
3003
+ if (typeof value === "object") {
3004
+ if (visited.has(value)) return;
3005
+ visited.add(value);
3006
+ for (const [k, v] of Object.entries(value)) {
3007
+ check(v, [...path, k], depth + 1);
3008
+ }
3009
+ }
3010
+ };
3011
+ check(input, [], 0);
3012
+ return {
3013
+ hasPII: fields.length > 0,
3014
+ fields
3015
+ };
3016
+ }
3017
+
3018
+ // libs/uipack/src/runtime/wrapper.ts
3019
+ function createTemplateHelpers() {
3020
+ let idCounter2 = 0;
3021
+ return {
3022
+ /**
3023
+ * Escape HTML special characters to prevent XSS
3024
+ */
3025
+ escapeHtml,
3026
+ /**
3027
+ * Format a date for display
3028
+ */
3029
+ formatDate: (date, format) => {
3030
+ const d = typeof date === "string" ? new Date(date) : date;
3031
+ if (isNaN(d.getTime())) return String(date);
3032
+ if (format === "iso") {
3033
+ return d.toISOString();
3034
+ }
3035
+ if (format === "time") {
3036
+ return d.toLocaleTimeString();
3037
+ }
3038
+ if (format === "datetime") {
3039
+ return d.toLocaleString();
3040
+ }
3041
+ return d.toLocaleDateString();
3042
+ },
3043
+ /**
3044
+ * Format a number as currency
3045
+ */
3046
+ formatCurrency: (amount, currency = "USD") => {
3047
+ return new Intl.NumberFormat("en-US", {
3048
+ style: "currency",
3049
+ currency
3050
+ }).format(amount);
3051
+ },
3052
+ /**
3053
+ * Generate a unique ID for DOM elements
3054
+ */
3055
+ uniqueId: (prefix = "mcp") => {
3056
+ return `${prefix}-${++idCounter2}-${Date.now().toString(36)}`;
3057
+ },
3058
+ /**
3059
+ * Safely embed JSON data in HTML
3060
+ * Escapes characters that could break out of script tags or HTML
3061
+ */
3062
+ jsonEmbed: (data) => {
3063
+ const json2 = JSON.stringify(data);
3064
+ if (json2 === void 0) {
3065
+ return "undefined";
3066
+ }
3067
+ return json2.replace(/</g, "\\u003c").replace(/>/g, "\\u003e").replace(/&/g, "\\u0026").replace(/'/g, "\\u0027");
3068
+ }
3069
+ };
3070
+ }
3071
+ function wrapToolUI(options) {
3072
+ const {
3073
+ content,
3074
+ toolName,
3075
+ input = {},
3076
+ output,
3077
+ structuredContent,
3078
+ csp,
3079
+ widgetAccessible = false,
3080
+ title,
3081
+ theme: themeOverrides,
3082
+ platform = OPENAI_PLATFORM,
3083
+ hostContext,
3084
+ sanitizeInput: sanitizeOption,
3085
+ rendererType,
3086
+ hydrate = false,
3087
+ // Disabled by default to prevent React hydration Error #418 in MCP clients
3088
+ skipCspMeta = false
3089
+ } = options;
3090
+ let sanitizedInput = input;
3091
+ if (sanitizeOption) {
3092
+ const sanitizeMode = sanitizeOption === true ? true : sanitizeOption;
3093
+ sanitizedInput = sanitizeInput(input, sanitizeMode);
3094
+ }
3095
+ const theme = themeOverrides ? mergeThemes(DEFAULT_THEME, themeOverrides) : DEFAULT_THEME;
3096
+ const useCdn = canUseCdn(platform);
3097
+ const useInline = needsInlineScripts(platform);
3098
+ const fontPreconnect = useCdn ? buildFontPreconnect() : "";
3099
+ const fontStylesheets = useCdn ? buildFontStylesheets({ inter: true }) : "";
3100
+ const scripts = buildCdnScripts({
3101
+ tailwind: platform.supportsTailwind,
3102
+ htmx: false,
3103
+ alpine: false,
3104
+ icons: false,
3105
+ inline: useInline
3106
+ });
3107
+ const frameworkScripts = buildFrameworkRuntimeScripts({
3108
+ rendererType,
3109
+ hydrate,
3110
+ platform
3111
+ });
3112
+ const themeCss = buildThemeCss(theme);
3113
+ const customCss = theme.customCss || "";
3114
+ const styleBlock = platform.supportsTailwind ? `<style type="text/tailwindcss">
3115
+ @theme {
3116
+ ${themeCss}
3117
+ }
3118
+ ${customCss}
3119
+ </style>` : "";
3120
+ const cspMetaTag = skipCspMeta ? "" : buildCSPMetaTag(csp);
3121
+ const dataScript = buildDataInjectionScript({
3122
+ toolName,
3123
+ input: sanitizedInput,
3124
+ output,
3125
+ structuredContent,
3126
+ widgetAccessible,
3127
+ hostContext
3128
+ });
3129
+ const pageTitle = title || `${escapeHtml(toolName)} - Tool Result`;
3130
+ return `<!DOCTYPE html>
3131
+ <html lang="en">
3132
+ <head>
3133
+ <meta charset="UTF-8">
3134
+ <meta name="viewport" content="width=device-width, initial-scale=1.0">
3135
+ <title>${pageTitle}</title>
3136
+ ${cspMetaTag}
3137
+
3138
+ <!-- Fonts -->
3139
+ ${fontPreconnect}
3140
+ ${fontStylesheets}
3141
+
3142
+ <!-- Tailwind CSS -->
3143
+ ${scripts}
3144
+ ${styleBlock}
3145
+
3146
+ <!-- Framework Runtime -->
3147
+ ${frameworkScripts}
3148
+
3149
+ <!-- Tool Data -->
3150
+ ${dataScript}
3151
+
3152
+ <!-- MCP Bridge Runtime -->
3153
+ ${MCP_BRIDGE_RUNTIME}
3154
+ </head>
3155
+ <body class="bg-background text-text-primary font-sans antialiased">
3156
+ ${content}
3157
+ </body>
3158
+ </html>`;
3159
+ }
3160
+ function buildFrameworkRuntimeScripts(options) {
3161
+ const { rendererType, hydrate, platform } = options;
3162
+ if (!rendererType || rendererType === "html" || rendererType === "html-fallback") {
3163
+ return "";
3164
+ }
3165
+ if (!hydrate) {
3166
+ return "";
3167
+ }
3168
+ if (rendererType === "react" || rendererType === "mdx") {
3169
+ const useCdn = canUseCdn(platform);
3170
+ if (useCdn) {
3171
+ return `
3172
+ <!-- React Runtime (for hydration) -->
3173
+ <script crossorigin src="https://unpkg.com/react@18/umd/react.production.min.js"></script>
3174
+ <script crossorigin src="https://unpkg.com/react-dom@18/umd/react-dom.production.min.js"></script>
3175
+ <script>
3176
+ // Hydration script for React/MDX components
3177
+ (function() {
3178
+ document.addEventListener('DOMContentLoaded', function() {
3179
+ var hydratables = document.querySelectorAll('[data-hydrate], [data-mdx-hydrate]');
3180
+ if (hydratables.length > 0 && window.__frontmcp_components) {
3181
+ hydratables.forEach(function(el) {
3182
+ var componentName = el.getAttribute('data-hydrate');
3183
+ var propsJson = el.getAttribute('data-props');
3184
+ var props = propsJson ? JSON.parse(propsJson) : {};
3185
+
3186
+ if (window.__frontmcp_components[componentName]) {
3187
+ try {
3188
+ ReactDOM.hydrateRoot(el, React.createElement(
3189
+ window.__frontmcp_components[componentName],
3190
+ props
3191
+ ));
3192
+ } catch (e) {
3193
+ console.error('[FrontMCP] Hydration failed:', e);
3194
+ }
3195
+ }
3196
+ });
3197
+ }
3198
+ });
3199
+ })();
3200
+ </script>`;
3201
+ } else {
3202
+ return `
3203
+ <!-- React hydration not available on blocked-network platforms -->
3204
+ <script>
3205
+ console.warn('[FrontMCP] React hydration disabled - platform does not support external scripts');
3206
+ </script>`;
3207
+ }
3208
+ }
3209
+ return "";
3210
+ }
3211
+ function buildDataInjectionScript(options) {
3212
+ const { toolName, input, output, structuredContent, widgetAccessible, hostContext } = options;
3213
+ const helpers = createTemplateHelpers();
3214
+ const contextData = {
3215
+ theme: hostContext?.theme || "light",
3216
+ displayMode: hostContext?.displayMode || "inline",
3217
+ ...hostContext
3218
+ };
3219
+ return `<script>
3220
+ // Tool metadata
3221
+ window.__mcpToolName = ${helpers.jsonEmbed(toolName)};
3222
+ window.__mcpToolInput = ${helpers.jsonEmbed(input)};
3223
+ window.__mcpToolOutput = ${helpers.jsonEmbed(output)};
3224
+ window.__mcpStructuredContent = ${helpers.jsonEmbed(structuredContent)};
3225
+ window.__mcpWidgetAccessible = ${helpers.jsonEmbed(widgetAccessible)};
3226
+ window.__mcpHostContext = ${helpers.jsonEmbed(contextData)};
3227
+ </script>`;
3228
+ }
3229
+ function wrapToolUIUniversal(options) {
3230
+ const {
3231
+ content,
3232
+ toolName,
3233
+ input = {},
3234
+ output,
3235
+ structuredContent,
3236
+ csp,
3237
+ widgetAccessible = false,
3238
+ title,
3239
+ theme: themeOverrides,
3240
+ includeBridge = true,
3241
+ inlineScripts = false,
3242
+ rendererType,
3243
+ hydrate = false,
3244
+ // Disabled by default to prevent React hydration Error #418 in MCP clients
3245
+ skipCspMeta = false,
3246
+ resourceMode = inlineScripts ? "inline" : "cdn"
3247
+ } = options;
3248
+ const useInlineScripts = resourceMode === "inline" || inlineScripts;
3249
+ const theme = themeOverrides ? mergeThemes(DEFAULT_THEME, themeOverrides) : DEFAULT_THEME;
3250
+ const fontPreconnect = useInlineScripts ? "" : buildFontPreconnect();
3251
+ const fontStylesheets = useInlineScripts ? "" : buildFontStylesheets({ inter: true });
3252
+ const scripts = buildCdnScripts({
3253
+ tailwind: true,
3254
+ htmx: false,
3255
+ alpine: false,
3256
+ icons: false,
3257
+ inline: useInlineScripts
3258
+ });
3259
+ const themeCss = buildThemeCss(theme);
3260
+ const customCss = theme.customCss || "";
3261
+ const styleBlock = `<style type="text/tailwindcss">
3262
+ @theme {
3263
+ ${themeCss}
3264
+ }
3265
+ ${customCss}
3266
+ </style>`;
3267
+ const cspMetaTag = skipCspMeta ? "" : buildCSPMetaTag(csp);
3268
+ const dataScript = buildDataInjectionScript({
3269
+ toolName,
3270
+ input,
3271
+ output,
3272
+ structuredContent,
3273
+ widgetAccessible
3274
+ });
3275
+ const frameworkScripts = buildFrameworkRuntimeScriptsUniversal({
3276
+ rendererType,
3277
+ hydrate,
3278
+ inlineScripts: useInlineScripts,
3279
+ resourceMode
3280
+ });
3281
+ const bridgeScript = includeBridge ? BRIDGE_SCRIPT_TAGS.universal : "";
3282
+ const pageTitle = title || `${escapeHtml(toolName)} - Tool Result`;
3283
+ return `<!DOCTYPE html>
3284
+ <html lang="en">
3285
+ <head>
3286
+ <meta charset="UTF-8">
3287
+ <meta name="viewport" content="width=device-width, initial-scale=1.0">
3288
+ <title>${pageTitle}</title>
3289
+ ${cspMetaTag}
3290
+
3291
+ <!-- Fonts -->
3292
+ ${fontPreconnect}
3293
+ ${fontStylesheets}
3294
+
3295
+ <!-- Tailwind CSS -->
3296
+ ${scripts}
3297
+ ${styleBlock}
3298
+
3299
+ <!-- Framework Runtime -->
3300
+ ${frameworkScripts}
3301
+
3302
+ <!-- Tool Data -->
3303
+ ${dataScript}
3304
+
3305
+ <!-- FrontMCP Bridge (Universal - Auto-detects host platform) -->
3306
+ ${bridgeScript}
3307
+ </head>
3308
+ <body class="bg-background text-text-primary font-sans antialiased">
3309
+ ${content}
3310
+ </body>
3311
+ </html>`;
3312
+ }
3313
+ function buildFrameworkRuntimeScriptsUniversal(options) {
3314
+ const { rendererType, hydrate, inlineScripts, resourceMode = "cdn" } = options;
3315
+ if (!rendererType || rendererType === "html" || rendererType === "html-fallback") {
3316
+ return "";
3317
+ }
3318
+ const useInline = resourceMode === "inline" || inlineScripts;
3319
+ if (rendererType === "react" || rendererType === "mdx") {
3320
+ if (!useInline) {
3321
+ const reactScripts = `
3322
+ <!-- React 19 Runtime (ES modules from esm.sh) -->
3323
+ <script type="module">
3324
+ import React from 'https://esm.sh/react@19';
3325
+ import { createRoot } from 'https://esm.sh/react-dom@19/client';
3326
+ window.React = React;
3327
+ window.ReactDOM = { createRoot };
3328
+ </script>`;
3329
+ const mdxScripts = rendererType === "mdx" ? `
3330
+ <!-- MDX Runtime (CDN mode) -->
3331
+ <script type="module">
3332
+ import * as runtime from 'https://esm.sh/@mdx-js/react@3?bundle';
3333
+ window.MDXRuntime = runtime;
3334
+ </script>` : "";
3335
+ const hydrationScript = hydrate ? `
3336
+ <script>
3337
+ // Hydration script for React/MDX components
3338
+ (function() {
3339
+ document.addEventListener('DOMContentLoaded', function() {
3340
+ var hydratables = document.querySelectorAll('[data-hydrate], [data-mdx-hydrate]');
3341
+ if (hydratables.length > 0 && window.__frontmcp_components) {
3342
+ hydratables.forEach(function(el) {
3343
+ var componentName = el.getAttribute('data-hydrate');
3344
+ var propsJson = el.getAttribute('data-props');
3345
+ var props = propsJson ? JSON.parse(propsJson) : {};
3346
+
3347
+ if (window.__frontmcp_components[componentName]) {
3348
+ try {
3349
+ ReactDOM.hydrateRoot(el, React.createElement(
3350
+ window.__frontmcp_components[componentName],
3351
+ props
3352
+ ));
3353
+ } catch (e) {
3354
+ console.error('[FrontMCP] Hydration failed:', e);
3355
+ }
3356
+ }
3357
+ });
3358
+ }
3359
+ });
3360
+ })();
3361
+ </script>` : "";
3362
+ return reactScripts + mdxScripts + hydrationScript;
3363
+ } else {
3364
+ return `
3365
+ <!-- React/MDX runtime disabled (inline/blocked-network mode) -->
3366
+ <script>
3367
+ console.warn('[FrontMCP] React/MDX runtime disabled - inline scripts mode (network blocked). SSR content is static.');
3368
+ </script>`;
3369
+ }
3370
+ }
3371
+ return "";
3372
+ }
3373
+ function wrapToolUIMinimal(options) {
3374
+ const {
3375
+ content,
3376
+ toolName,
3377
+ input = {},
3378
+ output,
3379
+ structuredContent,
3380
+ csp,
3381
+ widgetAccessible = false,
3382
+ title,
3383
+ skipCspMeta = false
3384
+ } = options;
3385
+ const helpers = createTemplateHelpers();
3386
+ const cspMetaTag = skipCspMeta ? "" : buildCSPMetaTag(csp);
3387
+ const contextData = { theme: "light", displayMode: "inline" };
3388
+ const dataScript = `<script>
3389
+ window.__mcpToolName = ${helpers.jsonEmbed(toolName)};
3390
+ window.__mcpToolInput = ${helpers.jsonEmbed(input)};
3391
+ window.__mcpToolOutput = ${helpers.jsonEmbed(output)};
3392
+ window.__mcpStructuredContent = ${helpers.jsonEmbed(structuredContent)};
3393
+ window.__mcpWidgetAccessible = ${helpers.jsonEmbed(widgetAccessible)};
3394
+ window.__mcpHostContext = ${helpers.jsonEmbed(contextData)};
3395
+ </script>`;
3396
+ const pageTitle = title || `${escapeHtml(toolName)} - Tool Result`;
3397
+ return `<!DOCTYPE html>
3398
+ <html lang="en">
3399
+ <head>
3400
+ <meta charset="UTF-8">
3401
+ <meta name="viewport" content="width=device-width, initial-scale=1.0">
3402
+ <title>${pageTitle}</title>
3403
+ ${cspMetaTag}
3404
+ ${dataScript}
3405
+ ${MCP_BRIDGE_RUNTIME}
3406
+ </head>
3407
+ <body>
3408
+ ${content}
3409
+ </body>
3410
+ </html>`;
3411
+ }
3412
+ function wrapLeanWidgetShell(options) {
3413
+ const { toolName, uiConfig, title, theme: themeOverrides } = options;
3414
+ const theme = themeOverrides ? mergeThemes(DEFAULT_THEME, themeOverrides) : DEFAULT_THEME;
3415
+ const fontPreconnect = buildFontPreconnect();
3416
+ const fontStylesheets = buildFontStylesheets({ inter: true });
3417
+ const tailwindScript = buildCdnScripts({
3418
+ tailwind: true,
3419
+ htmx: false,
3420
+ alpine: false,
3421
+ icons: false,
3422
+ inline: false
3423
+ });
3424
+ const themeCss = buildThemeCss(theme);
3425
+ const customCss = theme.customCss || "";
3426
+ const styleBlock = `<style type="text/tailwindcss">
3427
+ @theme {
3428
+ ${themeCss}
3429
+ }
3430
+ ${customCss}
3431
+ </style>`;
3432
+ const placeholderContent = `
3433
+ <div id="frontmcp-widget-root" class="flex items-center justify-center min-h-[200px] p-4">
3434
+ <div class="text-center text-gray-500">
3435
+ <svg class="animate-spin mx-auto mb-2" style="width: 1.5rem; height: 1.5rem; color: #9ca3af;" xmlns="http://www.w3.org/2000/svg" fill="none" viewBox="0 0 24 24">
3436
+ <circle style="opacity: 0.25;" cx="12" cy="12" r="10" stroke="currentColor" stroke-width="4"></circle>
3437
+ <path style="opacity: 0.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>
3438
+ </svg>
3439
+ <p class="text-sm">Loading widget...</p>
3440
+ </div>
3441
+ </div>
3442
+ `;
3443
+ const toolMetaScript = `<script>
3444
+ // Lean widget shell for inline mode
3445
+ // Actual widget content comes in tool response via ui/html
3446
+ window.__mcpToolName = ${JSON.stringify(toolName)};
3447
+ window.__mcpWidgetAccessible = ${JSON.stringify(uiConfig.widgetAccessible ?? false)};
3448
+ window.__mcpLeanShell = true;
3449
+ </script>`;
3450
+ const bridgeScript = BRIDGE_SCRIPT_TAGS.universal;
3451
+ const injectorScript = `<script>
3452
+ // Lean shell injector for inline mode
3453
+ // Uses FrontMCP Bridge (platform-agnostic) to detect tool response HTML
3454
+ (function() {
3455
+ var injected = false;
3456
+
3457
+ function injectWidget(metadata) {
3458
+ if (injected) return;
3459
+
3460
+ // Check for ui/html in metadata
3461
+ var html = null;
3462
+
3463
+ if (metadata) {
3464
+ // Try different possible locations for the HTML
3465
+ html = metadata['ui/html'] || metadata['openai/html'] || metadata.html;
3466
+ }
3467
+
3468
+ if (html && typeof html === 'string') {
3469
+ injected = true;
3470
+ console.log('[FrontMCP] Lean shell: Injecting inline widget HTML (' + html.length + ' chars)');
3471
+
3472
+ // Replace entire document with the full React widget HTML
3473
+ document.open();
3474
+ document.write(html);
3475
+ document.close();
3476
+ return true;
3477
+ }
3478
+ return false;
3479
+ }
3480
+
3481
+ // Wait for bridge to be ready, then subscribe
3482
+ function subscribeAndInject() {
3483
+ var bridge = window.FrontMcpBridge;
3484
+ if (!bridge) {
3485
+ console.warn('[FrontMCP] Lean shell: Bridge not found');
3486
+ return;
3487
+ }
3488
+
3489
+ // Check if data already available (via getToolResponseMetadata)
3490
+ if (typeof bridge.getToolResponseMetadata === 'function') {
3491
+ var existing = bridge.getToolResponseMetadata();
3492
+ if (existing && injectWidget(existing)) {
3493
+ return; // Already injected
3494
+ }
3495
+ }
3496
+
3497
+ // Subscribe to metadata changes (via onToolResponseMetadata)
3498
+ if (typeof bridge.onToolResponseMetadata === 'function') {
3499
+ console.log('[FrontMCP] Lean shell: Subscribing to tool response metadata');
3500
+ bridge.onToolResponseMetadata(function(metadata) {
3501
+ console.log('[FrontMCP] Lean shell: Received tool response metadata');
3502
+ injectWidget(metadata);
3503
+ });
3504
+ } else {
3505
+ console.warn('[FrontMCP] Lean shell: onToolResponseMetadata not available on bridge');
3506
+ }
3507
+ }
3508
+
3509
+ // Wait for bridge:ready event
3510
+ window.addEventListener('bridge:ready', function() {
3511
+ console.log('[FrontMCP] Lean shell: Bridge ready, setting up injector');
3512
+ subscribeAndInject();
3513
+ });
3514
+
3515
+ // Also try immediately in case bridge is already ready
3516
+ if (window.FrontMcpBridge && window.FrontMcpBridge.initialized) {
3517
+ subscribeAndInject();
3518
+ }
3519
+
3520
+ // Fallback: poll for bridge if event doesn't fire
3521
+ var bridgeCheckAttempts = 0;
3522
+ var bridgeCheckInterval = setInterval(function() {
3523
+ bridgeCheckAttempts++;
3524
+ if (window.FrontMcpBridge) {
3525
+ clearInterval(bridgeCheckInterval);
3526
+ if (!injected) {
3527
+ subscribeAndInject();
3528
+ }
3529
+ } else if (bridgeCheckAttempts >= 100) {
3530
+ // 10 second timeout
3531
+ clearInterval(bridgeCheckInterval);
3532
+ console.warn('[FrontMCP] Lean shell: Timeout waiting for bridge');
3533
+ }
3534
+ }, 100);
3535
+ })();
3536
+ </script>`;
3537
+ const spinnerCss = `<style>
3538
+ @keyframes spin { to { transform: rotate(360deg); } }
3539
+ .animate-spin { animation: spin 1s linear infinite; }
3540
+ </style>`;
3541
+ return `<!DOCTYPE html>
3542
+ <html lang="en">
3543
+ <head>
3544
+ <meta charset="UTF-8">
3545
+ <meta name="viewport" content="width=device-width, initial-scale=1.0">
3546
+ <title>${escapeHtml(title || toolName)}</title>
3547
+ ${fontPreconnect}
3548
+ ${fontStylesheets}
3549
+ ${tailwindScript}
3550
+ ${styleBlock}
3551
+ ${spinnerCss}
3552
+ ${toolMetaScript}
3553
+ ${bridgeScript}
3554
+ </head>
3555
+ <body class="bg-white font-sans antialiased">
3556
+ ${placeholderContent}
3557
+ ${injectorScript}
3558
+ </body>
3559
+ </html>`;
3560
+ }
3561
+ function wrapHybridWidgetShell(options) {
3562
+ const { toolName, uiConfig, title, theme: themeOverrides } = options;
3563
+ const theme = themeOverrides ? mergeThemes(DEFAULT_THEME, themeOverrides) : DEFAULT_THEME;
3564
+ const fontPreconnect = buildFontPreconnect();
3565
+ const fontStylesheets = buildFontStylesheets({ inter: true });
3566
+ const tailwindScript = buildCdnScripts({
3567
+ tailwind: true,
3568
+ htmx: false,
3569
+ alpine: false,
3570
+ icons: false,
3571
+ inline: false
3572
+ });
3573
+ const themeCss = buildThemeCss(theme);
3574
+ const customCss = theme.customCss || "";
3575
+ const styleBlock = `<style type="text/tailwindcss">
3576
+ @theme {
3577
+ ${themeCss}
3578
+ }
3579
+ ${customCss}
3580
+ </style>`;
3581
+ const placeholderContent = `
3582
+ <div id="frontmcp-widget-root" class="flex items-center justify-center min-h-[200px] p-4">
3583
+ <div class="text-center text-gray-500">
3584
+ <svg class="animate-spin mx-auto mb-2" style="width: 1.5rem; height: 1.5rem; color: #9ca3af;" xmlns="http://www.w3.org/2000/svg" fill="none" viewBox="0 0 24 24">
3585
+ <circle style="opacity: 0.25;" cx="12" cy="12" r="10" stroke="currentColor" stroke-width="4"></circle>
3586
+ <path style="opacity: 0.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>
3587
+ </svg>
3588
+ <p class="text-sm">Loading widget...</p>
3589
+ </div>
3590
+ </div>
3591
+ <div id="frontmcp-error" style="display: none; padding: 1rem; margin: 1rem; background: #fef2f2; border: 1px solid #fecaca; border-radius: 0.5rem; color: #dc2626; font-size: 0.875rem;"></div>
3592
+ `;
3593
+ const toolMetaScript = `<script>
3594
+ // Hybrid widget shell - component comes at tool call time
3595
+ window.__mcpToolName = ${JSON.stringify(toolName)};
3596
+ window.__mcpWidgetAccessible = ${JSON.stringify(uiConfig.widgetAccessible ?? false)};
3597
+ window.__mcpHybridShell = true;
3598
+ </script>`;
3599
+ const bridgeScript = BRIDGE_SCRIPT_TAGS.universal;
3600
+ const spinnerCss = `<style>
3601
+ @keyframes spin { to { transform: rotate(360deg); } }
3602
+ .animate-spin { animation: spin 1s linear infinite; }
3603
+ </style>`;
3604
+ const hybridRuntimeScript = `
3605
+ <!-- FrontMCP Hybrid Widget Runtime -->
3606
+ <script type="module">
3607
+ // ============================================
3608
+ // 1. Import React 19 from esm.sh
3609
+ // ============================================
3610
+ import React from 'https://esm.sh/react@19';
3611
+ import ReactDOM from 'https://esm.sh/react-dom@19/client';
3612
+
3613
+ // Make React available globally
3614
+ window.React = React;
3615
+ window.ReactDOM = ReactDOM;
3616
+
3617
+ // ============================================
3618
+ // 2. Provide webpack namespace objects for transpiled components
3619
+ // ============================================
3620
+ window.external_react_namespaceObject = React;
3621
+ window.jsx_runtime_namespaceObject = {
3622
+ jsx: (type, props, key) => {
3623
+ if (key !== undefined) props = { ...props, key };
3624
+ return React.createElement(type, props);
3625
+ },
3626
+ jsxs: (type, props, key) => {
3627
+ if (key !== undefined) props = { ...props, key };
3628
+ return React.createElement(type, props);
3629
+ },
3630
+ Fragment: React.Fragment,
3631
+ };
3632
+ window.process = window.process || { env: { NODE_ENV: 'production' } };
3633
+
3634
+ // ============================================
3635
+ // 3. FrontMCP Hooks (platform-agnostic via bridge)
3636
+ // ============================================
3637
+ function useMcpBridgeContext() {
3638
+ return {
3639
+ bridge: window.FrontMcpBridge || null,
3640
+ loading: false,
3641
+ error: null,
3642
+ ready: window.FrontMcpBridge?.initialized ?? false,
3643
+ adapterId: window.FrontMcpBridge?.adapterId ?? 'unknown',
3644
+ capabilities: window.FrontMcpBridge?.capabilities ?? {},
3645
+ };
3646
+ }
3647
+
3648
+ function useToolOutput() {
3649
+ const [output, setOutput] = React.useState(null);
3650
+ React.useEffect(() => {
3651
+ const bridge = window.FrontMcpBridge;
3652
+ if (!bridge) return;
3653
+
3654
+ // Get initial output
3655
+ const initial = bridge.getToolOutput();
3656
+ if (initial) setOutput(initial);
3657
+
3658
+ // Subscribe to updates
3659
+ const unsubscribe = bridge.onToolResult((result) => {
3660
+ setOutput(result);
3661
+ });
3662
+ return unsubscribe;
3663
+ }, []);
3664
+ return output;
3665
+ }
3666
+
3667
+ function useToolInput() {
3668
+ const bridge = window.FrontMcpBridge;
3669
+ return bridge?.getToolInput() || window.__mcpToolInput || {};
3670
+ }
3671
+
3672
+ function useTheme() {
3673
+ const [theme, setTheme] = React.useState('light');
3674
+ React.useEffect(() => {
3675
+ const bridge = window.FrontMcpBridge;
3676
+ if (bridge?.getTheme) setTheme(bridge.getTheme());
3677
+ }, []);
3678
+ return theme;
3679
+ }
3680
+
3681
+ function useCallTool(toolName, options = {}) {
3682
+ const [loading, setLoading] = React.useState(false);
3683
+ const [error, setError] = React.useState(null);
3684
+
3685
+ const isAvailable = !!(
3686
+ (window.openai && typeof window.openai.callTool === 'function') ||
3687
+ (window.FrontMcpBridge && window.FrontMcpBridge.hasCapability('canCallTools'))
3688
+ );
3689
+
3690
+ const callTool = React.useCallback(async (args) => {
3691
+ setLoading(true);
3692
+ setError(null);
3693
+ try {
3694
+ let result;
3695
+ if (window.openai && typeof window.openai.callTool === 'function') {
3696
+ result = await window.openai.callTool(toolName, args || {});
3697
+ } else if (window.FrontMcpBridge) {
3698
+ result = await window.FrontMcpBridge.callTool(toolName, args || {});
3699
+ } else {
3700
+ throw new Error('Tool calling not available');
3701
+ }
3702
+ options.onSuccess?.(result);
3703
+ return result;
3704
+ } catch (err) {
3705
+ setError(err);
3706
+ options.onError?.(err);
3707
+ throw err;
3708
+ } finally {
3709
+ setLoading(false);
3710
+ }
3711
+ }, [toolName]);
3712
+
3713
+ return [callTool, { loading, error, available: isAvailable }];
3714
+ }
3715
+
3716
+ // ============================================
3717
+ // 4. FrontMCP UI Components
3718
+ // ============================================
3719
+ function Card({ title, subtitle, variant, size, className, children, footer }) {
3720
+ const baseClasses = 'rounded-lg border bg-bg-surface';
3721
+ const variantClasses = variant === 'elevated' ? 'shadow-md' : 'border-divider';
3722
+ const sizeClasses = { sm: 'p-3', md: 'p-4', lg: 'p-6' }[size || 'md'];
3723
+ return React.createElement('div', { className: [baseClasses, variantClasses, sizeClasses, className].filter(Boolean).join(' ') },
3724
+ (title || subtitle) && React.createElement('div', { className: 'mb-4' },
3725
+ title && React.createElement('h3', { className: 'text-lg font-semibold text-text-primary' }, title),
3726
+ subtitle && React.createElement('p', { className: 'text-sm text-text-secondary mt-1' }, subtitle)
3727
+ ),
3728
+ children,
3729
+ footer && React.createElement('div', { className: 'mt-4 pt-4 border-t border-divider' }, footer)
3730
+ );
3731
+ }
3732
+
3733
+ function Badge({ children, variant, size, pill }) {
3734
+ const baseClasses = 'inline-flex items-center font-medium';
3735
+ const variantClasses = {
3736
+ default: 'bg-bg-secondary text-text-primary',
3737
+ success: 'bg-green-100 text-green-800',
3738
+ warning: 'bg-yellow-100 text-yellow-800',
3739
+ info: 'bg-blue-100 text-blue-800',
3740
+ danger: 'bg-red-100 text-red-800',
3741
+ }[variant || 'default'];
3742
+ const sizeClasses = { sm: 'px-2 py-0.5 text-xs', md: 'px-2.5 py-1 text-sm', lg: 'px-3 py-1.5 text-base' }[size || 'md'];
3743
+ const pillClasses = pill ? 'rounded-full' : 'rounded-md';
3744
+ return React.createElement('span', {
3745
+ className: [baseClasses, variantClasses, sizeClasses, pillClasses].filter(Boolean).join(' ')
3746
+ }, children);
3747
+ }
3748
+
3749
+ function Button({ children, variant, size, disabled, onClick, className }) {
3750
+ const baseClasses = 'inline-flex items-center justify-center rounded-md font-medium transition-colors';
3751
+ const variantClasses = {
3752
+ primary: 'bg-primary text-white hover:bg-primary/90',
3753
+ secondary: 'bg-bg-secondary text-text-primary hover:bg-bg-secondary/80',
3754
+ outline: 'border border-divider bg-transparent hover:bg-bg-secondary',
3755
+ ghost: 'bg-transparent hover:bg-bg-secondary',
3756
+ }[variant || 'primary'];
3757
+ const sizeClasses = { sm: 'px-3 py-1.5 text-sm', md: 'px-4 py-2 text-base', lg: 'px-6 py-3 text-lg' }[size || 'md'];
3758
+ const disabledClasses = disabled ? 'opacity-50 cursor-not-allowed' : '';
3759
+ return React.createElement('button', {
3760
+ type: 'button',
3761
+ className: [baseClasses, variantClasses, sizeClasses, disabledClasses, className].filter(Boolean).join(' '),
3762
+ disabled,
3763
+ onClick,
3764
+ }, children);
3765
+ }
3766
+
3767
+ // Expose via react_namespaceObject for transpiled components
3768
+ window.react_namespaceObject = {
3769
+ ...React,
3770
+ useMcpBridgeContext,
3771
+ useToolOutput,
3772
+ useToolInput,
3773
+ useTheme,
3774
+ useCallTool,
3775
+ Card,
3776
+ Badge,
3777
+ Button,
3778
+ McpBridgeProvider: ({ children }) => children,
3779
+ };
3780
+ window.react_dom_namespaceObject = ReactDOM;
3781
+
3782
+ // Template helpers
3783
+ window.__frontmcp_helpers = {
3784
+ escapeHtml: (str) => String(str).replace(/[&<>"']/g, c => ({'&':'&amp;','<':'&lt;','>':'&gt;','"':'&quot;',"'":'&#39;'})[c] || c),
3785
+ formatDate: (d) => new Date(d).toLocaleDateString(),
3786
+ formatCurrency: (a, c) => new Intl.NumberFormat('en-US', {style:'currency',currency:c||'USD'}).format(a),
3787
+ };
3788
+
3789
+ // Component-specific helpers (for weather demo, etc.)
3790
+ window.iconMap = {
3791
+ sunny: '\\u2600\\uFE0F', cloudy: '\\u2601\\uFE0F', rainy: '\\uD83C\\uDF27\\uFE0F',
3792
+ snowy: '\\u2744\\uFE0F', stormy: '\\u26C8\\uFE0F', windy: '\\uD83D\\uDCA8', foggy: '\\uD83C\\uDF2B\\uFE0F',
3793
+ };
3794
+ window.getConditionBadgeVariant = function(c) {
3795
+ switch(c) { case 'sunny': return 'success'; case 'rainy': case 'snowy': return 'info'; case 'stormy': return 'warning'; default: return 'default'; }
3796
+ };
3797
+
3798
+ console.log('[FrontMCP Hybrid] React 19 runtime loaded with hooks and components');
3799
+
3800
+ // ============================================
3801
+ // 5. Dynamic Renderer
3802
+ // ============================================
3803
+ let currentComponent = null;
3804
+ let reactRoot = null;
3805
+
3806
+ function hideLoader() {
3807
+ const loader = document.querySelector('#frontmcp-widget-root > div');
3808
+ if (loader) loader.style.display = 'none';
3809
+ }
3810
+
3811
+ function showError(message) {
3812
+ const errorEl = document.getElementById('frontmcp-error');
3813
+ if (errorEl) {
3814
+ errorEl.textContent = message;
3815
+ errorEl.style.display = 'block';
3816
+ }
3817
+ hideLoader();
3818
+ }
3819
+
3820
+ function render() {
3821
+ const bridge = window.FrontMcpBridge;
3822
+ const output = bridge?.getToolOutput();
3823
+
3824
+ if (!currentComponent) {
3825
+ console.log('[FrontMCP Hybrid] No component loaded yet');
3826
+ return false;
3827
+ }
3828
+
3829
+ if (!output) {
3830
+ console.log('[FrontMCP Hybrid] No data available yet');
3831
+ return false;
3832
+ }
3833
+
3834
+ const root = document.getElementById('frontmcp-widget-root');
3835
+ if (!root) return false;
3836
+
3837
+ const props = {
3838
+ input: bridge.getToolInput() || {},
3839
+ output: output,
3840
+ structuredContent: bridge.getStructuredContent(),
3841
+ helpers: window.__frontmcp_helpers,
3842
+ };
3843
+
3844
+ try {
3845
+ hideLoader();
3846
+ const element = React.createElement(currentComponent, props);
3847
+ if (!reactRoot) {
3848
+ reactRoot = ReactDOM.createRoot(root);
3849
+ }
3850
+ reactRoot.render(element);
3851
+ console.log('[FrontMCP Hybrid] Component rendered with data');
3852
+ return true;
3853
+ } catch (err) {
3854
+ console.error('[FrontMCP Hybrid] Render failed:', err);
3855
+ showError('Rendering failed: ' + err.message);
3856
+ return false;
3857
+ }
3858
+ }
3859
+
3860
+ async function loadComponent(payload) {
3861
+ if (!payload?.code) {
3862
+ console.warn('[FrontMCP Hybrid] No component code in payload');
3863
+ return;
3864
+ }
3865
+
3866
+ console.log('[FrontMCP Hybrid] Loading component, type:', payload.type);
3867
+
3868
+ try {
3869
+ // Import component via blob URL
3870
+ const blob = new Blob([payload.code], { type: 'application/javascript' });
3871
+ const url = URL.createObjectURL(blob);
3872
+
3873
+ const module = await import(/* webpackIgnore: true */ url);
3874
+ currentComponent = module.default || window.__frontmcp_component;
3875
+ URL.revokeObjectURL(url);
3876
+
3877
+ if (!currentComponent) {
3878
+ throw new Error('Component not found in module');
3879
+ }
3880
+
3881
+ console.log('[FrontMCP Hybrid] Component loaded successfully');
3882
+ render();
3883
+ } catch (err) {
3884
+ console.error('[FrontMCP Hybrid] Failed to load component:', err);
3885
+ showError('Failed to load component: ' + err.message);
3886
+ }
3887
+ }
3888
+
3889
+ // ============================================
3890
+ // 6. Bridge Integration
3891
+ // ============================================
3892
+ async function initializeBridge() {
3893
+ // Wait for bridge to be ready
3894
+ if (!window.FrontMcpBridge?.initialized) {
3895
+ await new Promise(resolve => {
3896
+ window.addEventListener('bridge:ready', resolve, { once: true });
3897
+ // Fallback timeout
3898
+ setTimeout(resolve, 5000);
3899
+ });
3900
+ }
3901
+
3902
+ const bridge = window.FrontMcpBridge;
3903
+ if (!bridge) {
3904
+ console.error('[FrontMCP Hybrid] Bridge not available');
3905
+ showError('Bridge initialization failed');
3906
+ return;
3907
+ }
3908
+
3909
+ console.log('[FrontMCP Hybrid] Bridge ready, adapter:', bridge.adapterId);
3910
+
3911
+ // Listen for component code in tool response metadata
3912
+ if (typeof bridge.onToolResponseMetadata === 'function') {
3913
+ bridge.onToolResponseMetadata(function(metadata) {
3914
+ console.log('[FrontMCP Hybrid] Received tool response metadata');
3915
+
3916
+ // Check for component payload
3917
+ const componentPayload = metadata['ui/component'];
3918
+ if (componentPayload) {
3919
+ loadComponent(componentPayload);
3920
+ }
3921
+ });
3922
+ }
3923
+
3924
+ // Listen for data updates (for re-renders)
3925
+ if (typeof bridge.onToolResult === 'function') {
3926
+ bridge.onToolResult(function(result) {
3927
+ console.log('[FrontMCP Hybrid] Received tool result update');
3928
+ render();
3929
+ });
3930
+ }
3931
+
3932
+ // Check if data already available (e.g., page refresh)
3933
+ const existingMetadata = bridge.getToolResponseMetadata?.();
3934
+ if (existingMetadata?.['ui/component']) {
3935
+ loadComponent(existingMetadata['ui/component']);
3936
+ }
3937
+ }
3938
+
3939
+ // Start initialization
3940
+ initializeBridge();
3941
+ </script>`;
3942
+ return `<!DOCTYPE html>
3943
+ <html lang="en">
3944
+ <head>
3945
+ <meta charset="UTF-8">
3946
+ <meta name="viewport" content="width=device-width, initial-scale=1.0">
3947
+ <title>${escapeHtml(title || toolName)}</title>
3948
+ ${fontPreconnect}
3949
+ ${fontStylesheets}
3950
+ ${tailwindScript}
3951
+ ${styleBlock}
3952
+ ${spinnerCss}
3953
+ ${toolMetaScript}
3954
+ ${bridgeScript}
3955
+ </head>
3956
+ <body class="bg-white font-sans antialiased">
3957
+ ${placeholderContent}
3958
+ ${hybridRuntimeScript}
3959
+ </body>
3960
+ </html>`;
3961
+ }
3962
+ function wrapStaticWidgetUniversal(options) {
3963
+ const {
3964
+ toolName,
3965
+ ssrContent,
3966
+ uiConfig,
3967
+ title,
3968
+ theme: themeOverrides,
3969
+ rendererType,
3970
+ componentCode,
3971
+ embeddedData,
3972
+ selfContained = false
3973
+ } = options;
3974
+ const theme = themeOverrides ? mergeThemes(DEFAULT_THEME, themeOverrides) : DEFAULT_THEME;
3975
+ const fontPreconnect = buildFontPreconnect();
3976
+ const fontStylesheets = buildFontStylesheets({ inter: true });
3977
+ const scripts = buildCdnScripts({
3978
+ tailwind: true,
3979
+ htmx: false,
3980
+ alpine: false,
3981
+ icons: false,
3982
+ inline: false
3983
+ });
3984
+ const themeCss = buildThemeCss(theme);
3985
+ const customCss = theme.customCss || "";
3986
+ const styleBlock = `<style type="text/tailwindcss">
3987
+ @theme {
3988
+ ${themeCss}
3989
+ }
3990
+ ${customCss}
3991
+ </style>`;
3992
+ const cspMetaTag = "";
3993
+ const isReactBased = rendererType === "react" || rendererType === "mdx";
3994
+ const hasEmbeddedData = embeddedData && (embeddedData.output !== void 0 || embeddedData.input !== void 0);
3995
+ const includeBridge = !selfContained && !hasEmbeddedData;
3996
+ const bridgeScript = includeBridge ? BRIDGE_SCRIPT_TAGS.universal : "";
3997
+ const helpers = createTemplateHelpers();
3998
+ const toolNameScript = selfContained && hasEmbeddedData ? `<script>
3999
+ // Tool metadata (self-contained inline mode - no bridge, no wrapper interference)
4000
+ window.__mcpToolName = ${helpers.jsonEmbed(toolName)};
4001
+ window.__mcpWidgetAccessible = ${helpers.jsonEmbed(uiConfig.widgetAccessible ?? false)};
4002
+ // Embedded data for immediate rendering
4003
+ window.__mcpToolInput = ${helpers.jsonEmbed(embeddedData.input ?? {})};
4004
+ window.__mcpToolOutput = ${helpers.jsonEmbed(embeddedData.output)};
4005
+ window.__mcpStructuredContent = ${helpers.jsonEmbed(embeddedData.structuredContent)};
4006
+ // Flags for self-contained mode
4007
+ window.__mcpDataEmbedded = true;
4008
+ window.__mcpSelfContained = true;
4009
+ // No bridge included - React component handles state internally via hooks
4010
+ </script>` : hasEmbeddedData ? `<script>
4011
+ // Tool metadata (inline mode - data embedded, backward compat)
4012
+ window.__mcpToolName = ${helpers.jsonEmbed(toolName)};
4013
+ window.__mcpWidgetAccessible = ${helpers.jsonEmbed(uiConfig.widgetAccessible ?? false)};
4014
+ window.__mcpToolInput = ${helpers.jsonEmbed(embeddedData.input ?? {})};
4015
+ window.__mcpToolOutput = ${helpers.jsonEmbed(embeddedData.output)};
4016
+ window.__mcpStructuredContent = ${helpers.jsonEmbed(embeddedData.structuredContent)};
4017
+ window.__mcpDataEmbedded = true;
4018
+ </script>` : `<script>
4019
+ // Tool metadata (static mode - data injected by host at runtime)
4020
+ window.__mcpToolName = ${helpers.jsonEmbed(toolName)};
4021
+ window.__mcpWidgetAccessible = ${helpers.jsonEmbed(uiConfig.widgetAccessible ?? false)};
4022
+ // Data will be provided by host platform:
4023
+ // - OpenAI: window.openai.toolOutput
4024
+ // - FrontMCP Bridge: window.__mcpToolOutput, window.__mcpStructuredContent
4025
+ </script>`;
4026
+ const reactModuleScript = isReactBased && componentCode ? `
4027
+ <!-- FrontMCP React Widget Runtime (Single ES Module) -->
4028
+ <script type="module">
4029
+ // ============================================
4030
+ // 1. Import React 19 from esm.sh
4031
+ // ============================================
4032
+ import React from 'https://esm.sh/react@19';
4033
+ import ReactDOM from 'https://esm.sh/react-dom@19/client';
4034
+
4035
+ // Make React available globally for the component code
4036
+ window.React = React;
4037
+ window.ReactDOM = ReactDOM;
4038
+
4039
+ // ============================================
4040
+ // 1b. Provide ALL webpack namespace objects
4041
+ // ============================================
4042
+ // Webpack generates different namespace variable names when bundling:
4043
+ // - external_react_namespaceObject: React marked as external
4044
+ // - jsx_runtime_namespaceObject: react/jsx-runtime
4045
+ // We must provide ALL of these for transpiled components to work.
4046
+
4047
+ // external_react_namespaceObject - for React imports (useState, useEffect, etc.)
4048
+ window.external_react_namespaceObject = React;
4049
+
4050
+ // jsx_runtime_namespaceObject - for JSX transformation (jsx, jsxs functions)
4051
+ window.jsx_runtime_namespaceObject = {
4052
+ jsx: (type, props, key) => {
4053
+ if (key !== undefined) props = { ...props, key };
4054
+ return React.createElement(type, props);
4055
+ },
4056
+ jsxs: (type, props, key) => {
4057
+ if (key !== undefined) props = { ...props, key };
4058
+ return React.createElement(type, props);
4059
+ },
4060
+ Fragment: React.Fragment,
4061
+ };
4062
+
4063
+ // process.env - for development mode checks
4064
+ window.process = window.process || { env: { NODE_ENV: 'production' } };
4065
+
4066
+ // ============================================
4067
+ // 1c. Component-specific helpers
4068
+ // ============================================
4069
+ // These are module-level variables that get lost when calling .toString() on the component.
4070
+ // For the weather component, we need iconMap and getConditionBadgeVariant.
4071
+
4072
+ window.iconMap = {
4073
+ sunny: '\u2600\uFE0F',
4074
+ cloudy: '\u2601\uFE0F',
4075
+ rainy: '\u{1F327}\uFE0F',
4076
+ snowy: '\u2744\uFE0F',
4077
+ stormy: '\u26C8\uFE0F',
4078
+ windy: '\u{1F4A8}',
4079
+ foggy: '\u{1F32B}\uFE0F',
4080
+ };
4081
+
4082
+ window.getConditionBadgeVariant = function(conditions) {
4083
+ switch (conditions) {
4084
+ case 'sunny': return 'success';
4085
+ case 'rainy':
4086
+ case 'snowy': return 'info';
4087
+ case 'stormy': return 'warning';
4088
+ default: return 'default';
4089
+ }
4090
+ };
4091
+
4092
+ // ============================================
4093
+ // 2. Provide FrontMCP hooks on the react namespace
4094
+ // ============================================
4095
+ // Transpiled components may call react_namespaceObject.useMcpBridgeContext, etc.
4096
+ // These are FrontMCP hooks that get bundled with React imports.
4097
+ // We provide stub implementations that work with the client-side data.
4098
+
4099
+ // State storage for hooks
4100
+ const hookState = {
4101
+ toolOutput: null,
4102
+ toolInput: null,
4103
+ theme: 'light',
4104
+ ready: false,
4105
+ };
4106
+
4107
+ // useMcpBridgeContext - returns context about the bridge
4108
+ function useMcpBridgeContext() {
4109
+ return {
4110
+ bridge: window.__frontmcp?.bridge || null,
4111
+ loading: false,
4112
+ error: null,
4113
+ ready: hookState.ready,
4114
+ adapterId: 'openai',
4115
+ capabilities: { canCallTools: true, canSendMessages: false },
4116
+ };
4117
+ }
4118
+
4119
+ // useToolOutput - returns the tool output
4120
+ function useToolOutput() {
4121
+ const [output, setOutput] = React.useState(hookState.toolOutput);
4122
+ React.useEffect(() => {
4123
+ // Update when toolOutput changes
4124
+ const checkOutput = () => {
4125
+ const newOutput = getToolOutput();
4126
+ if (newOutput && newOutput !== output) {
4127
+ setOutput(newOutput);
4128
+ hookState.toolOutput = newOutput;
4129
+ }
4130
+ };
4131
+ const interval = setInterval(checkOutput, 100);
4132
+ checkOutput();
4133
+ return () => clearInterval(interval);
4134
+ }, []);
4135
+ return output;
4136
+ }
4137
+
4138
+ // useToolInput - returns the tool input
4139
+ function useToolInput() {
4140
+ return hookState.toolInput || window.__mcpToolInput || {};
4141
+ }
4142
+
4143
+ // useTheme - returns current theme
4144
+ function useTheme() {
4145
+ const [theme, setTheme] = React.useState(hookState.theme);
4146
+ React.useEffect(() => {
4147
+ // Try to detect theme from host
4148
+ if (window.openai?.theme) {
4149
+ setTheme(window.openai.theme);
4150
+ }
4151
+ }, []);
4152
+ return theme;
4153
+ }
4154
+
4155
+ // useCallTool - returns a function to call tools from within widgets
4156
+ // Supports multiple environments:
4157
+ // - OpenAI: Uses window.openai.callTool directly
4158
+ // - FrontMCP Bridge: Uses window.__frontmcp.callTool
4159
+ // - Other: Falls back gracefully
4160
+ function useCallTool(toolName, options = {}) {
4161
+ const [loading, setLoading] = React.useState(false);
4162
+ const [error, setError] = React.useState(null);
4163
+
4164
+ // Check availability once
4165
+ const isAvailable = !!(
4166
+ (window.openai && typeof window.openai.callTool === 'function') ||
4167
+ (window.__frontmcp && typeof window.__frontmcp.callTool === 'function')
4168
+ );
4169
+
4170
+ const callTool = React.useCallback(async (args) => {
4171
+ setLoading(true);
4172
+ setError(null);
4173
+ try {
4174
+ let result;
4175
+
4176
+ // Priority 1: OpenAI SDK (most reliable in OpenAI iframe)
4177
+ if (window.openai && typeof window.openai.callTool === 'function') {
4178
+ console.log('[FrontMCP] useCallTool: Using OpenAI SDK for', toolName);
4179
+ result = await window.openai.callTool(toolName, args || {});
4180
+ } else if (window.__frontmcp && typeof window.__frontmcp.callTool === 'function') {
4181
+ // Priority 2: FrontMCP bridge
4182
+ console.log('[FrontMCP] useCallTool: Using FrontMCP bridge for', toolName);
4183
+ result = await window.__frontmcp.callTool(toolName, args || {});
4184
+ } else {
4185
+ // Not available - log warning and return null
4186
+ console.warn(
4187
+ '[FrontMCP] useCallTool: No tool calling mechanism available. ' +
4188
+ 'Tool: "' + toolName + '". Widget is display-only.'
4189
+ );
4190
+ const notAvailableError = new Error('Tool calling not available in this environment');
4191
+ setError(notAvailableError);
4192
+ options.onError?.(notAvailableError);
4193
+ return null;
4194
+ }
4195
+
4196
+ // Normalize result: ensure structuredContent is available for component callbacks
4197
+ // OpenAI returns raw tool output directly, but components may expect { structuredContent: ... }
4198
+ // This ensures both direct access (result.temperature) and wrapped access (result.structuredContent.temperature) work
4199
+ const normalizedResult = {
4200
+ ...result,
4201
+ structuredContent: result.structuredContent ?? result,
4202
+ };
4203
+
4204
+ // For static mode: Update global state so hooks (useToolOutput) pick up the change
4205
+ // For self-contained/inline mode: Skip this - React component handles state internally via setOutput
4206
+ // Updating global state in inline mode could trigger OpenAI's wrapper to overwrite our React component
4207
+ if (!window.__mcpSelfContained && !window.__mcpDataEmbedded) {
4208
+ window.__mcpToolOutput = normalizedResult.structuredContent;
4209
+ window.__mcpStructuredContent = normalizedResult.structuredContent;
4210
+ }
4211
+
4212
+ console.log('[FrontMCP] useCallTool: Tool returned, normalized result:', normalizedResult);
4213
+ options.onSuccess?.(normalizedResult);
4214
+ return normalizedResult;
4215
+ } catch (err) {
4216
+ console.error('[FrontMCP] useCallTool error:', err);
4217
+ setError(err);
4218
+ options.onError?.(err);
4219
+ throw err;
4220
+ } finally {
4221
+ setLoading(false);
4222
+ }
4223
+ }, [toolName]);
4224
+
4225
+ return [callTool, { loading, error, available: isAvailable }];
4226
+ }
4227
+
4228
+ // ============================================
4229
+ // 2b. UI Components (Card, Badge, etc.)
4230
+ // ============================================
4231
+ // These may be bundled from @frontmcp/ui/react and referenced via namespace.
4232
+ // We provide simple stub implementations.
4233
+
4234
+ function Card({ title, subtitle, variant, size, className, children, footer }) {
4235
+ const baseClasses = 'rounded-lg border bg-bg-surface';
4236
+ const variantClasses = variant === 'elevated' ? 'shadow-md' : 'border-divider';
4237
+ const sizeClasses = { sm: 'p-3', md: 'p-4', lg: 'p-6' }[size || 'md'];
4238
+
4239
+ return React.createElement('div', { className: [baseClasses, variantClasses, sizeClasses, className].filter(Boolean).join(' ') },
4240
+ (title || subtitle) && React.createElement('div', { className: 'mb-4' },
4241
+ title && React.createElement('h3', { className: 'text-lg font-semibold text-text-primary' }, title),
4242
+ subtitle && React.createElement('p', { className: 'text-sm text-text-secondary mt-1' }, subtitle)
4243
+ ),
4244
+ children,
4245
+ footer && React.createElement('div', { className: 'mt-4 pt-4 border-t border-divider' }, footer)
4246
+ );
4247
+ }
4248
+
4249
+ function Badge({ children, variant, size, pill }) {
4250
+ const baseClasses = 'inline-flex items-center font-medium';
4251
+ const variantClasses = {
4252
+ default: 'bg-bg-secondary text-text-primary',
4253
+ success: 'bg-green-100 text-green-800',
4254
+ warning: 'bg-yellow-100 text-yellow-800',
4255
+ info: 'bg-blue-100 text-blue-800',
4256
+ danger: 'bg-red-100 text-red-800',
4257
+ }[variant || 'default'];
4258
+ const sizeClasses = { sm: 'px-2 py-0.5 text-xs', md: 'px-2.5 py-1 text-sm', lg: 'px-3 py-1.5 text-base' }[size || 'md'];
4259
+ const pillClasses = pill ? 'rounded-full' : 'rounded-md';
4260
+
4261
+ return React.createElement('span', {
4262
+ className: [baseClasses, variantClasses, sizeClasses, pillClasses].filter(Boolean).join(' ')
4263
+ }, children);
4264
+ }
4265
+
4266
+ function Button({ children, variant, size, disabled, onClick, className }) {
4267
+ const baseClasses = 'inline-flex items-center justify-center rounded-md font-medium transition-colors';
4268
+ const variantClasses = {
4269
+ primary: 'bg-primary text-white hover:bg-primary/90',
4270
+ secondary: 'bg-bg-secondary text-text-primary hover:bg-bg-secondary/80',
4271
+ outline: 'border border-divider bg-transparent hover:bg-bg-secondary',
4272
+ ghost: 'bg-transparent hover:bg-bg-secondary',
4273
+ }[variant || 'primary'];
4274
+ const sizeClasses = { sm: 'px-3 py-1.5 text-sm', md: 'px-4 py-2 text-base', lg: 'px-6 py-3 text-lg' }[size || 'md'];
4275
+ const disabledClasses = disabled ? 'opacity-50 cursor-not-allowed' : '';
4276
+
4277
+ return React.createElement('button', {
4278
+ type: 'button',
4279
+ className: [baseClasses, variantClasses, sizeClasses, disabledClasses, className].filter(Boolean).join(' '),
4280
+ disabled,
4281
+ onClick,
4282
+ }, children);
4283
+ }
4284
+
4285
+ // Provide webpack-style namespace objects that transpiled components may reference
4286
+ // This fixes "react_namespaceObject is not defined" errors
4287
+ // Include both React exports AND FrontMCP hooks/components
4288
+ window.react_namespaceObject = {
4289
+ ...React,
4290
+ // FrontMCP hooks
4291
+ useMcpBridgeContext,
4292
+ useToolOutput,
4293
+ useToolInput,
4294
+ useTheme,
4295
+ useCallTool,
4296
+ // FrontMCP UI components
4297
+ Card,
4298
+ Badge,
4299
+ Button,
4300
+ // Also provide as 'McpBridgeProvider' stub (no-op for client)
4301
+ McpBridgeProvider: ({ children }) => children,
4302
+ };
4303
+ window.react_dom_namespaceObject = ReactDOM;
4304
+
4305
+ console.log('[FrontMCP] React 19 loaded from esm.sh with FrontMCP hooks');
4306
+
4307
+ // ============================================
4308
+ // 2. Define the Component
4309
+ // ============================================
4310
+ // Note: The component may reference react_namespaceObject which is now available
4311
+ ${componentCode}
4312
+
4313
+ // ============================================
4314
+ // 3. Helper Functions
4315
+ // ============================================
4316
+ function getComponent() {
4317
+ return window.__frontmcp_component;
4318
+ }
4319
+
4320
+ function getToolOutput() {
4321
+ // Try OpenAI's toolOutput first
4322
+ if (window.openai && window.openai.toolOutput) {
4323
+ return window.openai.toolOutput;
4324
+ }
4325
+ // Try FrontMCP bridge
4326
+ if (window.__mcpToolOutput) {
4327
+ return window.__mcpToolOutput;
4328
+ }
4329
+ // Try __frontmcp namespace
4330
+ if (window.__frontmcp && window.__frontmcp.toolOutput) {
4331
+ return window.__frontmcp.toolOutput;
4332
+ }
4333
+ return null;
4334
+ }
4335
+
4336
+ function showLoader() {
4337
+ const loader = document.getElementById('frontmcp-loader');
4338
+ if (loader) loader.style.display = 'flex';
4339
+ }
4340
+
4341
+ function hideLoader() {
4342
+ const loader = document.getElementById('frontmcp-loader');
4343
+ if (loader) loader.style.display = 'none';
4344
+ }
4345
+
4346
+ function showError(message) {
4347
+ const errorEl = document.getElementById('frontmcp-error');
4348
+ if (errorEl) {
4349
+ errorEl.textContent = message;
4350
+ errorEl.style.display = 'block';
4351
+ }
4352
+ hideLoader();
4353
+ }
4354
+
4355
+ // ============================================
4356
+ // 4. Render Function
4357
+ // ============================================
4358
+ function renderComponent() {
4359
+ const Component = getComponent();
4360
+ if (!Component) {
4361
+ console.warn('[FrontMCP] No component registered for client-side rendering');
4362
+ showError('Component not found');
4363
+ return false;
4364
+ }
4365
+
4366
+ const output = getToolOutput();
4367
+ if (!output) {
4368
+ return false; // Not ready yet, keep polling
4369
+ }
4370
+
4371
+ // Find or create widget root
4372
+ // OpenAI may have removed our original root element during iframe setup
4373
+ // when their wrapper script overwrites #widget-root with a loading spinner
4374
+ let root = document.getElementById('frontmcp-widget-root');
4375
+ if (!root) {
4376
+ console.log('[FrontMCP] Widget root not found, creating new element');
4377
+
4378
+ // Look for OpenAI's container first - we should render inside it
4379
+ const openaiRoot = document.getElementById('widget-root');
4380
+
4381
+ if (openaiRoot) {
4382
+ // Clear OpenAI's wrapper content and create our root inside
4383
+ console.log('[FrontMCP] Found OpenAI widget-root, creating frontmcp-widget-root inside it');
4384
+ openaiRoot.innerHTML = '';
4385
+ root = document.createElement('div');
4386
+ root.id = 'frontmcp-widget-root';
4387
+ openaiRoot.appendChild(root);
4388
+ } else {
4389
+ // Fallback: create in body (for MCP Inspector, etc.)
4390
+ console.log('[FrontMCP] No OpenAI widget-root, creating in body');
4391
+ root = document.createElement('div');
4392
+ root.id = 'frontmcp-widget-root';
4393
+ document.body.innerHTML = '';
4394
+ document.body.appendChild(root);
4395
+ }
4396
+ }
4397
+
4398
+ // Ensure it's visible
4399
+ root.style.display = 'block';
4400
+
4401
+ try {
4402
+ // Build props
4403
+ const props = {
4404
+ input: window.__mcpToolInput || {},
4405
+ output: output,
4406
+ structuredContent: window.__mcpStructuredContent,
4407
+ helpers: {
4408
+ escapeHtml: (str) => String(str).replace(/[&<>"']/g, c => ({'&':'&amp;','<':'&lt;','>':'&gt;','"':'&quot;',"'":'&#39;'})[c] || c),
4409
+ formatDate: (d) => new Date(d).toLocaleDateString(),
4410
+ formatCurrency: (a, c) => new Intl.NumberFormat('en-US', {style:'currency',currency:c||'USD'}).format(a),
4411
+ }
4412
+ };
4413
+
4414
+ // Hide loader and show widget root before rendering
4415
+ hideLoader();
4416
+ root.style.display = 'block';
4417
+
4418
+ // Render with React 19
4419
+ const element = React.createElement(Component, props);
4420
+ const reactRoot = ReactDOM.createRoot(root);
4421
+ reactRoot.render(element);
4422
+ console.log('[FrontMCP] Component rendered successfully with data:', output);
4423
+
4424
+ // Mark React as mounted to prevent OpenAI wrapper from overwriting
4425
+ // This works with the renderContent override in the Tool Metadata script
4426
+ window.__frontmcp = window.__frontmcp || {};
4427
+ window.__frontmcp._reactMounted = true;
4428
+
4429
+ // For inline mode: prevent OpenAI's wrapper from re-rendering over us
4430
+ // The wrapper subscribes to bridge state changes and will overwrite our content
4431
+ // when bridge.setData is called. We disable this after initial render.
4432
+ if (window.__mcpDataEmbedded && window.__frontmcp && window.__frontmcp.bridge) {
4433
+ window.__frontmcp._inlineRendered = true;
4434
+
4435
+ // Override setData to no-op after inline render
4436
+ const originalSetData = window.__frontmcp.bridge.setData;
4437
+ if (originalSetData && !window.__frontmcp._setDataOverridden) {
4438
+ window.__frontmcp._setDataOverridden = true;
4439
+ window.__frontmcp.bridge.setData = function(data) {
4440
+ if (window.__frontmcp._inlineRendered) {
4441
+ console.log('[FrontMCP] Skipping bridge setData - inline mode already rendered');
4442
+ return;
4443
+ }
4444
+ return originalSetData.call(this, data);
4445
+ };
4446
+ }
4447
+ }
4448
+
4449
+ return true;
4450
+ } catch (e) {
4451
+ console.error('[FrontMCP] React rendering failed:', e);
4452
+ showError('Rendering failed: ' + e.message);
4453
+ return false;
4454
+ }
4455
+ }
4456
+
4457
+ // ============================================
4458
+ // 5. Main: Render immediately or poll for toolOutput
4459
+ // ============================================
4460
+ // For inline mode (embeddedData): data is already embedded, render immediately
4461
+ // For static mode: poll for window.openai.toolOutput
4462
+
4463
+ if (window.__mcpDataEmbedded) {
4464
+ // Inline mode: Data is embedded in HTML, render immediately
4465
+ console.log('[FrontMCP] Inline mode: data embedded, rendering immediately');
4466
+ if (!renderComponent()) {
4467
+ showError('Failed to render component');
4468
+ }
4469
+ } else {
4470
+ // MCP-resource mode: Poll for toolOutput from host platform
4471
+ showLoader();
4472
+
4473
+ let attempts = 0;
4474
+ const maxAttempts = 100; // 10 seconds max
4475
+ const pollInterval = setInterval(() => {
4476
+ attempts++;
4477
+ if (renderComponent()) {
4478
+ clearInterval(pollInterval);
4479
+ } else if (attempts >= maxAttempts) {
4480
+ clearInterval(pollInterval);
4481
+ if (!getToolOutput()) {
4482
+ console.warn('[FrontMCP] Timeout waiting for toolOutput');
4483
+ showError('Timeout waiting for data');
4484
+ }
4485
+ }
4486
+ }, 100);
4487
+ }
4488
+ </script>` : "";
4489
+ const pageTitle = title || `${escapeHtml(toolName)} - Tool Widget`;
4490
+ const loaderHtml = isReactBased && componentCode && !hasEmbeddedData ? `
4491
+ <!-- Loading State -->
4492
+ <div id="frontmcp-loader" style="display: flex; align-items: center; justify-content: center; padding: 2rem; gap: 0.5rem;">
4493
+ <svg class="animate-spin" style="width: 1.25rem; height: 1.25rem; color: #6b7280;" xmlns="http://www.w3.org/2000/svg" fill="none" viewBox="0 0 24 24">
4494
+ <circle style="opacity: 0.25;" cx="12" cy="12" r="10" stroke="currentColor" stroke-width="4"></circle>
4495
+ <path style="opacity: 0.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>
4496
+ </svg>
4497
+ <span style="color: #6b7280; font-size: 0.875rem;">Loading widget...</span>
4498
+ </div>
4499
+ <div id="frontmcp-error" style="display: none; padding: 1rem; margin: 1rem; background: #fef2f2; border: 1px solid #fecaca; border-radius: 0.5rem; color: #dc2626; font-size: 0.875rem;"></div>
4500
+ ` : "";
4501
+ const widgetRootStyle = hasEmbeddedData ? "" : "display: none;";
4502
+ const wrappedContent = isReactBased && componentCode ? `${loaderHtml}<div id="frontmcp-widget-root" style="${widgetRootStyle}">${ssrContent}</div>` : ssrContent;
4503
+ return `<!DOCTYPE html>
4504
+ <html lang="en">
4505
+ <head>
4506
+ <meta charset="UTF-8">
4507
+ <meta name="viewport" content="width=device-width, initial-scale=1.0">
4508
+ <title>${pageTitle}</title>
4509
+ ${cspMetaTag}
4510
+
4511
+ <!-- Fonts -->
4512
+ ${fontPreconnect}
4513
+ ${fontStylesheets}
4514
+
4515
+ <!-- Tailwind CSS -->
4516
+ ${scripts}
4517
+ ${styleBlock}
4518
+
4519
+ <!-- Spinner Animation -->
4520
+ <style>
4521
+ @keyframes spin { to { transform: rotate(360deg); } }
4522
+ .animate-spin { animation: spin 1s linear infinite; }
4523
+ </style>
4524
+
4525
+ <!-- Tool Metadata -->
4526
+ ${toolNameScript}
4527
+
4528
+ <!-- FrontMCP Bridge (Universal - Reads data from host at runtime) -->
4529
+ ${bridgeScript}
4530
+ </head>
4531
+ <body class="bg-background text-text-primary font-sans antialiased">
4532
+ ${wrappedContent}
4533
+ ${reactModuleScript}
4534
+ </body>
4535
+ </html>`;
4536
+ }
4537
+ function buildOpenAIMeta(options) {
4538
+ const meta = {};
4539
+ if (options.widgetAccessible) {
4540
+ meta["openai/widgetAccessible"] = true;
4541
+ }
4542
+ if (options.widgetDescription) {
4543
+ meta["openai/widgetDescription"] = options.widgetDescription;
4544
+ }
4545
+ if (options.csp) {
4546
+ const cspConfig = {};
4547
+ if (options.csp.connectDomains?.length) {
4548
+ cspConfig["connect_domains"] = options.csp.connectDomains;
4549
+ }
4550
+ if (options.csp.resourceDomains?.length) {
4551
+ cspConfig["resource_domains"] = options.csp.resourceDomains;
4552
+ }
4553
+ if (Object.keys(cspConfig).length > 0) {
4554
+ meta["openai/widgetCSP"] = cspConfig;
4555
+ }
4556
+ }
4557
+ if (options.displayMode && options.displayMode !== "inline") {
4558
+ meta["openai/displayMode"] = options.displayMode;
4559
+ }
4560
+ return meta;
4561
+ }
4562
+ function getToolUIMimeType(platform = "generic") {
4563
+ switch (platform) {
4564
+ case "openai":
4565
+ return "text/html+skybridge";
4566
+ case "ext-apps":
4567
+ return "text/html+mcp";
4568
+ default:
4569
+ return "text/html";
4570
+ }
4571
+ }
4572
+ var CLOUDFLARE_CDN = {
4573
+ tailwindCss: "https://cdnjs.cloudflare.com/ajax/libs/tailwindcss/2.2.19/tailwind.min.css",
4574
+ htmx: "https://cdnjs.cloudflare.com/ajax/libs/htmx/2.0.4/htmx.min.js",
4575
+ alpinejs: "https://cdnjs.cloudflare.com/ajax/libs/alpinejs/3.14.3/cdn.min.js"
4576
+ };
4577
+ function wrapToolUIForClaude(options) {
4578
+ const { content, toolName, input = {}, output, title, includeHtmx = false, includeAlpine = false } = options;
4579
+ const tailwindCss = `<link href="${CLOUDFLARE_CDN.tailwindCss}" rel="stylesheet">`;
4580
+ const htmxScript = includeHtmx ? `<script src="${CLOUDFLARE_CDN.htmx}" crossorigin="anonymous"></script>` : "";
4581
+ const alpineScript = includeAlpine ? `<script src="${CLOUDFLARE_CDN.alpinejs}" crossorigin="anonymous" defer></script>` : "";
4582
+ const helpers = createTemplateHelpers();
4583
+ const dataScript = `<script>
4584
+ window.__mcpToolName = ${helpers.jsonEmbed(toolName)};
4585
+ window.__mcpToolInput = ${helpers.jsonEmbed(input)};
4586
+ window.__mcpToolOutput = ${helpers.jsonEmbed(output)};
4587
+ </script>`;
4588
+ const pageTitle = title || `${escapeHtml(toolName)} - Tool Result`;
4589
+ return `<!DOCTYPE html>
4590
+ <html lang="en">
4591
+ <head>
4592
+ <meta charset="UTF-8">
4593
+ <meta name="viewport" content="width=device-width, initial-scale=1.0">
4594
+ <title>${pageTitle}</title>
4595
+ ${tailwindCss}
4596
+ ${htmxScript}
4597
+ ${alpineScript}
4598
+ ${dataScript}
4599
+ </head>
4600
+ <body class="bg-gray-100 p-4">
4601
+ ${content}
4602
+ </body>
4603
+ </html>`;
4604
+ }
4605
+
4606
+ // libs/uipack/src/runtime/adapters/index.ts
4607
+ init_html_adapter();
4608
+ init_mdx_adapter();
4609
+ var adapterLoaders = {
4610
+ html: () => Promise.resolve().then(() => (init_html_adapter(), html_adapter_exports)).then((m) => m.createHtmlAdapter()),
4611
+ react: void 0,
4612
+ // React adapter is in @frontmcp/ui
4613
+ mdx: () => Promise.resolve().then(() => (init_mdx_adapter(), mdx_adapter_exports)).then((m) => m.createMdxAdapter()),
4614
+ markdown: () => Promise.resolve().then(() => (init_mdx_adapter(), mdx_adapter_exports)).then((m) => m.createMdxAdapter()),
4615
+ // Use MDX for markdown
4616
+ auto: void 0
4617
+ // Auto-detection handled by runtime
4618
+ };
4619
+ function getAdapterLoader(type) {
4620
+ return adapterLoaders[type];
4621
+ }
4622
+ async function loadAdapter(type) {
4623
+ const loader = getAdapterLoader(type);
4624
+ if (!loader) {
4625
+ return null;
4626
+ }
4627
+ return loader();
4628
+ }
4629
+
4630
+ // libs/uipack/src/runtime/renderer-runtime.ts
4631
+ var RendererRuntime = class {
4632
+ config;
4633
+ adapters = /* @__PURE__ */ new Map();
4634
+ state;
4635
+ updateCallbacks = [];
4636
+ bridgeUnsubscribe = null;
4637
+ constructor(config = {}) {
4638
+ this.config = config;
4639
+ this.state = {
4640
+ initialized: false,
4641
+ currentAdapter: null,
4642
+ currentTarget: null,
4643
+ context: {
4644
+ input: config.input ?? {},
4645
+ output: config.output,
4646
+ structuredContent: config.structuredContent,
4647
+ toolName: config.toolName ?? "unknown"
4648
+ }
4649
+ };
4650
+ }
4651
+ /**
4652
+ * Initialize the runtime.
4653
+ * Reads manifest, sets up Bridge listeners, and prepares adapters.
4654
+ */
4655
+ async init() {
4656
+ if (this.state.initialized) {
4657
+ return;
4658
+ }
4659
+ this.log("Initializing RendererRuntime");
4660
+ this.readManifest();
4661
+ this.readPageGlobals();
4662
+ this.setupBridgeListeners();
4663
+ this.state.initialized = true;
4664
+ this.log("RendererRuntime initialized", this.config.manifest);
4665
+ }
4666
+ /**
4667
+ * Get the current render context.
4668
+ */
4669
+ get context() {
4670
+ return { ...this.state.context };
4671
+ }
4672
+ /**
4673
+ * Get the manifest.
4674
+ */
4675
+ get manifest() {
4676
+ return this.config.manifest;
4677
+ }
4678
+ /**
4679
+ * Get the resolved UI type.
4680
+ */
4681
+ get uiType() {
4682
+ return this.config.manifest?.uiType ?? "auto";
4683
+ }
4684
+ /**
4685
+ * Render content to a target element.
4686
+ *
4687
+ * @param target - Element to render into
4688
+ * @param content - Content to render (optional, uses existing innerHTML)
4689
+ * @param options - Render options
4690
+ */
4691
+ async render(target, content, options) {
4692
+ const type = this.uiType;
4693
+ const contentToRender = content ?? target.innerHTML;
4694
+ this.log("Rendering", { type, hasContent: !!content, hydrate: options?.hydrate });
4695
+ const adapter = await this.getAdapter(type, contentToRender);
4696
+ if (!adapter) {
4697
+ return {
4698
+ success: false,
4699
+ error: `No adapter available for UI type: ${type}`
4700
+ };
4701
+ }
4702
+ this.state.currentAdapter = adapter;
4703
+ this.state.currentTarget = target;
4704
+ if (options?.hydrate && adapter.hydrate) {
4705
+ return adapter.hydrate(target, this.state.context);
4706
+ }
4707
+ if (adapter.renderToDOM) {
4708
+ return adapter.renderToDOM(contentToRender, target, this.state.context);
4709
+ }
4710
+ const html = await adapter.render(contentToRender, this.state.context);
4711
+ target.innerHTML = html;
4712
+ return { success: true, html };
4713
+ }
4714
+ /**
4715
+ * Update the render context and re-render if needed.
4716
+ */
4717
+ async updateContext(updates) {
4718
+ this.state.context = {
4719
+ ...this.state.context,
4720
+ ...updates
4721
+ };
4722
+ this.log("Context updated", updates);
4723
+ for (const callback of this.updateCallbacks) {
4724
+ try {
4725
+ callback(this.state.context);
4726
+ } catch (error) {
4727
+ console.error("[FrontMCP] Update callback error:", error);
4728
+ }
4729
+ }
4730
+ if (this.state.currentTarget && this.state.currentAdapter) {
4731
+ if (this.state.currentAdapter.update) {
4732
+ await this.state.currentAdapter.update(this.state.currentTarget, this.state.context);
4733
+ }
4734
+ }
4735
+ }
4736
+ /**
4737
+ * Subscribe to context updates.
4738
+ */
4739
+ onUpdate(callback) {
4740
+ this.updateCallbacks.push(callback);
4741
+ return () => {
4742
+ const index = this.updateCallbacks.indexOf(callback);
4743
+ if (index > -1) {
4744
+ this.updateCallbacks.splice(index, 1);
4745
+ }
4746
+ };
4747
+ }
4748
+ /**
4749
+ * Clean up resources.
4750
+ */
4751
+ destroy() {
4752
+ if (this.bridgeUnsubscribe) {
4753
+ this.bridgeUnsubscribe();
4754
+ this.bridgeUnsubscribe = null;
4755
+ }
4756
+ if (this.state.currentAdapter && this.state.currentTarget) {
4757
+ this.state.currentAdapter.destroy?.(this.state.currentTarget);
4758
+ }
4759
+ this.state.initialized = false;
4760
+ this.state.currentAdapter = null;
4761
+ this.state.currentTarget = null;
4762
+ this.updateCallbacks = [];
4763
+ }
4764
+ /**
4765
+ * Read manifest from page.
4766
+ */
4767
+ readManifest() {
4768
+ const manifestEl = document.getElementById("frontmcp-widget-manifest");
4769
+ if (manifestEl) {
4770
+ try {
4771
+ const manifest = JSON.parse(manifestEl.textContent ?? "{}");
4772
+ this.config.manifest = manifest;
4773
+ this.log("Loaded manifest from page", manifest);
4774
+ return;
4775
+ } catch {
4776
+ console.warn("[FrontMCP] Failed to parse manifest from page");
4777
+ }
4778
+ }
4779
+ const legacyManifestEl = document.getElementById("__mcp_manifest");
4780
+ if (legacyManifestEl) {
4781
+ try {
4782
+ const manifest = JSON.parse(legacyManifestEl.textContent ?? "{}");
4783
+ this.config.manifest = manifest;
4784
+ return;
4785
+ } catch {
4786
+ console.warn("[FrontMCP] Failed to parse legacy manifest from page");
4787
+ }
4788
+ }
4789
+ const win = window;
4790
+ if (win.__frontmcp?.widget?.manifest) {
4791
+ this.config.manifest = win.__frontmcp.widget.manifest;
4792
+ }
4793
+ }
4794
+ /**
4795
+ * Read initial data from page globals.
4796
+ */
4797
+ readPageGlobals() {
4798
+ const win = window;
4799
+ if (win.__mcpToolName) {
4800
+ this.state.context.toolName = win.__mcpToolName;
4801
+ } else if (this.config.manifest?.tool) {
4802
+ this.state.context.toolName = this.config.manifest.tool;
4803
+ }
4804
+ if (win.__mcpToolInput) {
4805
+ this.state.context.input = win.__mcpToolInput;
4806
+ } else if (win.openai?.toolInput) {
4807
+ this.state.context.input = win.openai.toolInput;
4808
+ }
4809
+ if (win.__mcpToolOutput !== void 0) {
4810
+ this.state.context.output = win.__mcpToolOutput;
4811
+ } else if (win.openai?.toolOutput !== void 0) {
4812
+ this.state.context.output = win.openai.toolOutput;
4813
+ }
4814
+ if (win.__mcpStructuredContent !== void 0) {
4815
+ this.state.context.structuredContent = win.__mcpStructuredContent;
4816
+ }
4817
+ }
4818
+ /**
4819
+ * Set up Bridge event listeners.
4820
+ */
4821
+ setupBridgeListeners() {
4822
+ const win = window;
4823
+ if (win.mcpBridge?.onToolResult) {
4824
+ this.bridgeUnsubscribe = win.mcpBridge.onToolResult((result) => {
4825
+ this.updateContext({ output: result });
4826
+ });
4827
+ }
4828
+ window.addEventListener("frontmcp:toolOutput", ((event) => {
4829
+ this.updateContext({ output: event.detail });
4830
+ }));
4831
+ }
4832
+ /**
4833
+ * Get or load an adapter for a UI type.
4834
+ */
4835
+ async getAdapter(type, content) {
4836
+ if (this.adapters.has(type)) {
4837
+ return this.adapters.get(type);
4838
+ }
4839
+ let resolvedType = type;
4840
+ if (type === "auto" && content) {
4841
+ resolvedType = this.detectType(content);
4842
+ this.log("Auto-detected type:", resolvedType);
4843
+ }
4844
+ if (this.adapters.has(resolvedType)) {
4845
+ return this.adapters.get(resolvedType);
4846
+ }
4847
+ const adapter = await loadAdapter(resolvedType);
4848
+ if (adapter) {
4849
+ this.adapters.set(resolvedType, adapter);
4850
+ if (resolvedType !== type) {
4851
+ this.adapters.set(type, adapter);
4852
+ }
4853
+ }
4854
+ return adapter;
4855
+ }
4856
+ /**
4857
+ * Auto-detect UI type from content.
4858
+ */
4859
+ detectType(content) {
4860
+ if (content.includes("React.createElement") || content.includes("jsx(") || /function\s+\w+\s*\([^)]*\)\s*\{[\s\S]*return\s*[\s\S]*</.test(content)) {
4861
+ return "react";
4862
+ }
4863
+ if (/^---[\s\S]*?---/.test(content) || /<[A-Z][a-zA-Z0-9]*[\s/>]/.test(content) || /^import\s+/m.test(content) || /^export\s+/m.test(content)) {
4864
+ return "mdx";
4865
+ }
4866
+ return "html";
4867
+ }
4868
+ /**
4869
+ * Log message if debug enabled.
4870
+ */
4871
+ log(message, ...args) {
4872
+ if (this.config.debug) {
4873
+ console.log(`[FrontMCP Runtime] ${message}`, ...args);
4874
+ }
4875
+ }
4876
+ };
4877
+ async function createRendererRuntime(config) {
4878
+ const runtime = new RendererRuntime(config);
4879
+ await runtime.init();
4880
+ return runtime;
4881
+ }
4882
+ async function bootstrapRendererRuntime() {
4883
+ if (document.readyState === "loading") {
4884
+ await new Promise((resolve) => {
4885
+ document.addEventListener("DOMContentLoaded", () => resolve());
4886
+ });
4887
+ }
4888
+ try {
4889
+ const runtime = await createRendererRuntime({ debug: false });
4890
+ const contentEl = document.querySelector("[data-frontmcp-widget]") || document.querySelector("[data-mcp-widget]") || document.body.firstElementChild;
4891
+ if (contentEl instanceof HTMLElement) {
4892
+ await runtime.render(contentEl, void 0, { hydrate: true });
4893
+ }
4894
+ const win = window;
4895
+ win.__frontmcp = win.__frontmcp ?? {};
4896
+ win.__frontmcp.runtime = runtime;
4897
+ return runtime;
4898
+ } catch (error) {
4899
+ console.error("[FrontMCP] Failed to bootstrap renderer runtime:", error);
4900
+ return null;
4901
+ }
4902
+ }
4903
+ function generateBootstrapScript() {
4904
+ return `
4905
+ <script>
4906
+ (function() {
4907
+ 'use strict';
4908
+
4909
+ // Wait for runtime to load
4910
+ function bootstrap() {
4911
+ if (window.__frontmcp && window.__frontmcp.bootstrapRuntime) {
4912
+ window.__frontmcp.bootstrapRuntime();
4913
+ } else {
4914
+ // Retry after short delay
4915
+ setTimeout(bootstrap, 50);
4916
+ }
4917
+ }
4918
+
4919
+ if (document.readyState === 'loading') {
4920
+ document.addEventListener('DOMContentLoaded', bootstrap);
4921
+ } else {
4922
+ bootstrap();
4923
+ }
4924
+ })();
4925
+ </script>
4926
+ `.trim();
4927
+ }
4928
+ export {
4929
+ DEFAULT_CDN_DOMAINS,
4930
+ DEFAULT_CSP_DIRECTIVES,
4931
+ FRONTMCP_BRIDGE_RUNTIME,
4932
+ HtmlRendererAdapter,
4933
+ MCP_BRIDGE_RUNTIME,
4934
+ MdxRendererAdapter,
4935
+ PII_PATTERNS,
4936
+ PLATFORM_BRIDGE_SCRIPTS,
4937
+ REDACTION_TOKENS,
4938
+ RESTRICTIVE_CSP_DIRECTIVES,
4939
+ RendererRuntime,
4940
+ adapterLoaders,
4941
+ bootstrapRendererRuntime,
4942
+ buildCSPDirectives,
4943
+ buildCSPMetaTag,
4944
+ buildOpenAICSP,
4945
+ buildOpenAIMeta,
4946
+ createHtmlAdapter,
4947
+ createMdxAdapter,
4948
+ createRendererRuntime,
4949
+ createSanitizer,
4950
+ createTemplateHelpers,
4951
+ detectPII,
4952
+ detectPIIType,
4953
+ generateBootstrapScript,
4954
+ generateBridgeIIFE as generateCustomBridge,
4955
+ getAdapterLoader,
4956
+ getMCPBridgeScript,
4957
+ getToolUIMimeType,
4958
+ isCreditCard,
4959
+ isEmail,
4960
+ isIPv4,
4961
+ isMCPBridgeSupported,
4962
+ isPhone,
4963
+ isSSN,
4964
+ loadAdapter,
4965
+ redactPIIFromText,
4966
+ sanitizeCSPDomains,
4967
+ sanitizeInput,
4968
+ validateCSPDomain,
4969
+ wrapHybridWidgetShell,
4970
+ wrapLeanWidgetShell,
4971
+ wrapStaticWidgetUniversal,
4972
+ wrapToolUI,
4973
+ wrapToolUIForClaude,
4974
+ wrapToolUIMinimal,
4975
+ wrapToolUIUniversal
4976
+ };