@frontmcp/ui 0.6.1 → 0.6.2

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 (292) hide show
  1. package/bridge/core/bridge-factory.d.ts +1 -0
  2. package/bridge/core/bridge-factory.d.ts.map +1 -1
  3. package/bridge/index.d.ts +1 -1
  4. package/bridge/index.d.ts.map +1 -1
  5. package/bridge/index.js +39 -881
  6. package/bundler/browser-components.d.ts +42 -0
  7. package/bundler/browser-components.d.ts.map +1 -0
  8. package/bundler/bundler.d.ts +78 -4
  9. package/bundler/bundler.d.ts.map +1 -1
  10. package/bundler/index.d.ts +8 -8
  11. package/bundler/index.d.ts.map +1 -1
  12. package/bundler/index.js +1315 -1854
  13. package/bundler/types.d.ts +188 -7
  14. package/bundler/types.d.ts.map +1 -1
  15. package/esm/bridge/{index.js → index.mjs} +40 -877
  16. package/esm/bundler/{index.js → index.mjs} +1391 -1895
  17. package/esm/{index.js → index.mjs} +215 -3091
  18. package/esm/layouts/{index.js → index.mjs} +3 -3
  19. package/esm/package.json +9 -8
  20. package/esm/react/index.mjs +1183 -0
  21. package/esm/renderers/index.mjs +611 -0
  22. package/esm/universal/{index.js → index.mjs} +266 -70
  23. package/index.d.ts +1 -4
  24. package/index.d.ts.map +1 -1
  25. package/index.js +208 -3113
  26. package/layouts/base.d.ts.map +1 -1
  27. package/layouts/index.js +3 -3
  28. package/layouts/presets.d.ts.map +1 -1
  29. package/package.json +9 -8
  30. package/react/Badge.d.ts.map +1 -1
  31. package/react/hooks/context.d.ts.map +1 -1
  32. package/react/index.d.ts +0 -1
  33. package/react/index.d.ts.map +1 -1
  34. package/react/index.js +57 -2001
  35. package/react/types.d.ts.map +1 -1
  36. package/renderers/index.d.ts +9 -4
  37. package/renderers/index.d.ts.map +1 -1
  38. package/renderers/index.js +328 -88
  39. package/renderers/mdx.renderer.d.ts +99 -0
  40. package/renderers/mdx.renderer.d.ts.map +1 -0
  41. package/renderers/react.renderer.d.ts +22 -13
  42. package/renderers/react.renderer.d.ts.map +1 -1
  43. package/renderers/transpiler.d.ts +49 -0
  44. package/renderers/transpiler.d.ts.map +1 -0
  45. package/universal/cached-runtime.d.ts +25 -1
  46. package/universal/cached-runtime.d.ts.map +1 -1
  47. package/universal/index.js +266 -70
  48. package/universal/runtime-builder.d.ts.map +1 -1
  49. package/universal/types.d.ts.map +1 -1
  50. package/web-components/elements/fmcp-input.d.ts.map +1 -1
  51. package/web-components/elements/fmcp-select.d.ts.map +1 -1
  52. package/web-components/index.d.ts +0 -1
  53. package/web-components/index.d.ts.map +1 -1
  54. package/bundler/cache.d.ts +0 -173
  55. package/bundler/cache.d.ts.map +0 -1
  56. package/bundler/file-cache/component-builder.d.ts +0 -167
  57. package/bundler/file-cache/component-builder.d.ts.map +0 -1
  58. package/bundler/file-cache/hash-calculator.d.ts +0 -155
  59. package/bundler/file-cache/hash-calculator.d.ts.map +0 -1
  60. package/bundler/file-cache/index.d.ts +0 -12
  61. package/bundler/file-cache/index.d.ts.map +0 -1
  62. package/bundler/file-cache/storage/filesystem.d.ts +0 -149
  63. package/bundler/file-cache/storage/filesystem.d.ts.map +0 -1
  64. package/bundler/file-cache/storage/index.d.ts +0 -11
  65. package/bundler/file-cache/storage/index.d.ts.map +0 -1
  66. package/bundler/file-cache/storage/interface.d.ts +0 -152
  67. package/bundler/file-cache/storage/interface.d.ts.map +0 -1
  68. package/bundler/file-cache/storage/redis.d.ts +0 -139
  69. package/bundler/file-cache/storage/redis.d.ts.map +0 -1
  70. package/bundler/sandbox/enclave-adapter.d.ts +0 -121
  71. package/bundler/sandbox/enclave-adapter.d.ts.map +0 -1
  72. package/bundler/sandbox/executor.d.ts +0 -14
  73. package/bundler/sandbox/executor.d.ts.map +0 -1
  74. package/bundler/sandbox/policy.d.ts +0 -62
  75. package/bundler/sandbox/policy.d.ts.map +0 -1
  76. package/esm/bridge/adapters/base-adapter.d.ts +0 -104
  77. package/esm/bridge/adapters/base-adapter.d.ts.map +0 -1
  78. package/esm/bridge/adapters/claude.adapter.d.ts +0 -67
  79. package/esm/bridge/adapters/claude.adapter.d.ts.map +0 -1
  80. package/esm/bridge/adapters/ext-apps.adapter.d.ts +0 -143
  81. package/esm/bridge/adapters/ext-apps.adapter.d.ts.map +0 -1
  82. package/esm/bridge/adapters/gemini.adapter.d.ts +0 -64
  83. package/esm/bridge/adapters/gemini.adapter.d.ts.map +0 -1
  84. package/esm/bridge/adapters/generic.adapter.d.ts +0 -56
  85. package/esm/bridge/adapters/generic.adapter.d.ts.map +0 -1
  86. package/esm/bridge/adapters/index.d.ts +0 -26
  87. package/esm/bridge/adapters/index.d.ts.map +0 -1
  88. package/esm/bridge/adapters/openai.adapter.d.ts +0 -65
  89. package/esm/bridge/adapters/openai.adapter.d.ts.map +0 -1
  90. package/esm/bridge/core/adapter-registry.d.ts +0 -122
  91. package/esm/bridge/core/adapter-registry.d.ts.map +0 -1
  92. package/esm/bridge/core/bridge-factory.d.ts +0 -199
  93. package/esm/bridge/core/bridge-factory.d.ts.map +0 -1
  94. package/esm/bridge/core/index.d.ts +0 -10
  95. package/esm/bridge/core/index.d.ts.map +0 -1
  96. package/esm/bridge/index.d.ts +0 -62
  97. package/esm/bridge/index.d.ts.map +0 -1
  98. package/esm/bridge/runtime/iife-generator.d.ts +0 -62
  99. package/esm/bridge/runtime/iife-generator.d.ts.map +0 -1
  100. package/esm/bridge/runtime/index.d.ts +0 -10
  101. package/esm/bridge/runtime/index.d.ts.map +0 -1
  102. package/esm/bridge/types.d.ts +0 -386
  103. package/esm/bridge/types.d.ts.map +0 -1
  104. package/esm/bundler/bundler.d.ts +0 -208
  105. package/esm/bundler/bundler.d.ts.map +0 -1
  106. package/esm/bundler/cache.d.ts +0 -173
  107. package/esm/bundler/cache.d.ts.map +0 -1
  108. package/esm/bundler/file-cache/component-builder.d.ts +0 -167
  109. package/esm/bundler/file-cache/component-builder.d.ts.map +0 -1
  110. package/esm/bundler/file-cache/hash-calculator.d.ts +0 -155
  111. package/esm/bundler/file-cache/hash-calculator.d.ts.map +0 -1
  112. package/esm/bundler/file-cache/index.d.ts +0 -12
  113. package/esm/bundler/file-cache/index.d.ts.map +0 -1
  114. package/esm/bundler/file-cache/storage/filesystem.d.ts +0 -149
  115. package/esm/bundler/file-cache/storage/filesystem.d.ts.map +0 -1
  116. package/esm/bundler/file-cache/storage/index.d.ts +0 -11
  117. package/esm/bundler/file-cache/storage/index.d.ts.map +0 -1
  118. package/esm/bundler/file-cache/storage/interface.d.ts +0 -152
  119. package/esm/bundler/file-cache/storage/interface.d.ts.map +0 -1
  120. package/esm/bundler/file-cache/storage/redis.d.ts +0 -139
  121. package/esm/bundler/file-cache/storage/redis.d.ts.map +0 -1
  122. package/esm/bundler/index.d.ts +0 -43
  123. package/esm/bundler/index.d.ts.map +0 -1
  124. package/esm/bundler/sandbox/enclave-adapter.d.ts +0 -121
  125. package/esm/bundler/sandbox/enclave-adapter.d.ts.map +0 -1
  126. package/esm/bundler/sandbox/executor.d.ts +0 -14
  127. package/esm/bundler/sandbox/executor.d.ts.map +0 -1
  128. package/esm/bundler/sandbox/policy.d.ts +0 -62
  129. package/esm/bundler/sandbox/policy.d.ts.map +0 -1
  130. package/esm/bundler/types.d.ts +0 -702
  131. package/esm/bundler/types.d.ts.map +0 -1
  132. package/esm/components/alert.d.ts +0 -66
  133. package/esm/components/alert.d.ts.map +0 -1
  134. package/esm/components/alert.schema.d.ts +0 -98
  135. package/esm/components/alert.schema.d.ts.map +0 -1
  136. package/esm/components/avatar.d.ts +0 -77
  137. package/esm/components/avatar.d.ts.map +0 -1
  138. package/esm/components/avatar.schema.d.ts +0 -170
  139. package/esm/components/avatar.schema.d.ts.map +0 -1
  140. package/esm/components/badge.d.ts +0 -64
  141. package/esm/components/badge.d.ts.map +0 -1
  142. package/esm/components/badge.schema.d.ts +0 -91
  143. package/esm/components/badge.schema.d.ts.map +0 -1
  144. package/esm/components/button.d.ts +0 -100
  145. package/esm/components/button.d.ts.map +0 -1
  146. package/esm/components/button.schema.d.ts +0 -120
  147. package/esm/components/button.schema.d.ts.map +0 -1
  148. package/esm/components/card.d.ts +0 -53
  149. package/esm/components/card.d.ts.map +0 -1
  150. package/esm/components/card.schema.d.ts +0 -93
  151. package/esm/components/card.schema.d.ts.map +0 -1
  152. package/esm/components/form.d.ts +0 -212
  153. package/esm/components/form.d.ts.map +0 -1
  154. package/esm/components/form.schema.d.ts +0 -365
  155. package/esm/components/form.schema.d.ts.map +0 -1
  156. package/esm/components/index.d.ts +0 -29
  157. package/esm/components/index.d.ts.map +0 -1
  158. package/esm/components/list.d.ts +0 -121
  159. package/esm/components/list.d.ts.map +0 -1
  160. package/esm/components/list.schema.d.ts +0 -129
  161. package/esm/components/list.schema.d.ts.map +0 -1
  162. package/esm/components/modal.d.ts +0 -100
  163. package/esm/components/modal.d.ts.map +0 -1
  164. package/esm/components/modal.schema.d.ts +0 -151
  165. package/esm/components/modal.schema.d.ts.map +0 -1
  166. package/esm/components/table.d.ts +0 -91
  167. package/esm/components/table.d.ts.map +0 -1
  168. package/esm/components/table.schema.d.ts +0 -123
  169. package/esm/components/table.schema.d.ts.map +0 -1
  170. package/esm/index.d.ts +0 -40
  171. package/esm/index.d.ts.map +0 -1
  172. package/esm/layouts/base.d.ts +0 -86
  173. package/esm/layouts/base.d.ts.map +0 -1
  174. package/esm/layouts/index.d.ts +0 -8
  175. package/esm/layouts/index.d.ts.map +0 -1
  176. package/esm/layouts/presets.d.ts +0 -134
  177. package/esm/layouts/presets.d.ts.map +0 -1
  178. package/esm/pages/consent.d.ts +0 -117
  179. package/esm/pages/consent.d.ts.map +0 -1
  180. package/esm/pages/error.d.ts +0 -101
  181. package/esm/pages/error.d.ts.map +0 -1
  182. package/esm/pages/index.d.ts +0 -9
  183. package/esm/pages/index.d.ts.map +0 -1
  184. package/esm/pages/index.js +0 -1036
  185. package/esm/react/Alert.d.ts +0 -101
  186. package/esm/react/Alert.d.ts.map +0 -1
  187. package/esm/react/Badge.d.ts +0 -100
  188. package/esm/react/Badge.d.ts.map +0 -1
  189. package/esm/react/Button.d.ts +0 -108
  190. package/esm/react/Button.d.ts.map +0 -1
  191. package/esm/react/Card.d.ts +0 -103
  192. package/esm/react/Card.d.ts.map +0 -1
  193. package/esm/react/hooks/context.d.ts +0 -179
  194. package/esm/react/hooks/context.d.ts.map +0 -1
  195. package/esm/react/hooks/index.d.ts +0 -42
  196. package/esm/react/hooks/index.d.ts.map +0 -1
  197. package/esm/react/hooks/tools.d.ts +0 -284
  198. package/esm/react/hooks/tools.d.ts.map +0 -1
  199. package/esm/react/index.d.ts +0 -80
  200. package/esm/react/index.d.ts.map +0 -1
  201. package/esm/react/index.js +0 -3124
  202. package/esm/react/types.d.ts +0 -105
  203. package/esm/react/types.d.ts.map +0 -1
  204. package/esm/react/utils.d.ts +0 -43
  205. package/esm/react/utils.d.ts.map +0 -1
  206. package/esm/render/index.d.ts +0 -8
  207. package/esm/render/index.d.ts.map +0 -1
  208. package/esm/render/prerender.d.ts +0 -57
  209. package/esm/render/prerender.d.ts.map +0 -1
  210. package/esm/renderers/index.d.ts +0 -21
  211. package/esm/renderers/index.d.ts.map +0 -1
  212. package/esm/renderers/index.js +0 -381
  213. package/esm/renderers/react.adapter.d.ts +0 -70
  214. package/esm/renderers/react.adapter.d.ts.map +0 -1
  215. package/esm/renderers/react.renderer.d.ts +0 -96
  216. package/esm/renderers/react.renderer.d.ts.map +0 -1
  217. package/esm/universal/UniversalApp.d.ts +0 -108
  218. package/esm/universal/UniversalApp.d.ts.map +0 -1
  219. package/esm/universal/cached-runtime.d.ts +0 -115
  220. package/esm/universal/cached-runtime.d.ts.map +0 -1
  221. package/esm/universal/context.d.ts +0 -122
  222. package/esm/universal/context.d.ts.map +0 -1
  223. package/esm/universal/index.d.ts +0 -57
  224. package/esm/universal/index.d.ts.map +0 -1
  225. package/esm/universal/renderers/html.renderer.d.ts +0 -37
  226. package/esm/universal/renderers/html.renderer.d.ts.map +0 -1
  227. package/esm/universal/renderers/index.d.ts +0 -112
  228. package/esm/universal/renderers/index.d.ts.map +0 -1
  229. package/esm/universal/renderers/markdown.renderer.d.ts +0 -33
  230. package/esm/universal/renderers/markdown.renderer.d.ts.map +0 -1
  231. package/esm/universal/renderers/mdx.renderer.d.ts +0 -38
  232. package/esm/universal/renderers/mdx.renderer.d.ts.map +0 -1
  233. package/esm/universal/renderers/react.renderer.d.ts +0 -46
  234. package/esm/universal/renderers/react.renderer.d.ts.map +0 -1
  235. package/esm/universal/runtime-builder.d.ts +0 -33
  236. package/esm/universal/runtime-builder.d.ts.map +0 -1
  237. package/esm/universal/store.d.ts +0 -135
  238. package/esm/universal/store.d.ts.map +0 -1
  239. package/esm/universal/types.d.ts +0 -199
  240. package/esm/universal/types.d.ts.map +0 -1
  241. package/esm/web-components/core/attribute-parser.d.ts +0 -82
  242. package/esm/web-components/core/attribute-parser.d.ts.map +0 -1
  243. package/esm/web-components/core/base-element.d.ts +0 -197
  244. package/esm/web-components/core/base-element.d.ts.map +0 -1
  245. package/esm/web-components/core/index.d.ts +0 -9
  246. package/esm/web-components/core/index.d.ts.map +0 -1
  247. package/esm/web-components/elements/fmcp-alert.d.ts +0 -46
  248. package/esm/web-components/elements/fmcp-alert.d.ts.map +0 -1
  249. package/esm/web-components/elements/fmcp-badge.d.ts +0 -47
  250. package/esm/web-components/elements/fmcp-badge.d.ts.map +0 -1
  251. package/esm/web-components/elements/fmcp-button.d.ts +0 -117
  252. package/esm/web-components/elements/fmcp-button.d.ts.map +0 -1
  253. package/esm/web-components/elements/fmcp-card.d.ts +0 -53
  254. package/esm/web-components/elements/fmcp-card.d.ts.map +0 -1
  255. package/esm/web-components/elements/fmcp-input.d.ts +0 -96
  256. package/esm/web-components/elements/fmcp-input.d.ts.map +0 -1
  257. package/esm/web-components/elements/fmcp-select.d.ts +0 -100
  258. package/esm/web-components/elements/fmcp-select.d.ts.map +0 -1
  259. package/esm/web-components/elements/index.d.ts +0 -13
  260. package/esm/web-components/elements/index.d.ts.map +0 -1
  261. package/esm/web-components/index.d.ts +0 -50
  262. package/esm/web-components/index.d.ts.map +0 -1
  263. package/esm/web-components/register.d.ts +0 -57
  264. package/esm/web-components/register.d.ts.map +0 -1
  265. package/esm/web-components/types.d.ts +0 -122
  266. package/esm/web-components/types.d.ts.map +0 -1
  267. package/esm/widgets/index.d.ts +0 -8
  268. package/esm/widgets/index.d.ts.map +0 -1
  269. package/esm/widgets/index.js +0 -883
  270. package/esm/widgets/progress.d.ts +0 -133
  271. package/esm/widgets/progress.d.ts.map +0 -1
  272. package/esm/widgets/resource.d.ts +0 -163
  273. package/esm/widgets/resource.d.ts.map +0 -1
  274. package/pages/consent.d.ts +0 -117
  275. package/pages/consent.d.ts.map +0 -1
  276. package/pages/error.d.ts +0 -101
  277. package/pages/error.d.ts.map +0 -1
  278. package/pages/index.d.ts +0 -9
  279. package/pages/index.d.ts.map +0 -1
  280. package/pages/index.js +0 -1065
  281. package/react/utils.d.ts +0 -43
  282. package/react/utils.d.ts.map +0 -1
  283. package/widgets/index.d.ts +0 -8
  284. package/widgets/index.d.ts.map +0 -1
  285. package/widgets/index.js +0 -910
  286. package/widgets/progress.d.ts +0 -133
  287. package/widgets/progress.d.ts.map +0 -1
  288. package/widgets/resource.d.ts +0 -163
  289. package/widgets/resource.d.ts.map +0 -1
  290. /package/esm/components/{index.js → index.mjs} +0 -0
  291. /package/esm/render/{index.js → index.mjs} +0 -0
  292. /package/esm/web-components/{index.js → index.mjs} +0 -0
@@ -719,7 +719,7 @@ function getAlignmentClasses(alignment) {
719
719
  };
720
720
  return alignMap[alignment];
721
721
  }
722
- function getBackgroundClasses(background, theme) {
722
+ function getBackgroundClasses(background) {
723
723
  switch (background) {
724
724
  case "gradient":
725
725
  return "bg-gradient-to-br from-primary to-secondary";
@@ -764,7 +764,7 @@ function buildBodyAttrs(attrs) {
764
764
  function baseLayout(content, options) {
765
765
  const {
766
766
  title,
767
- pageType = "custom",
767
+ pageType: _pageType = "custom",
768
768
  size = "md",
769
769
  alignment = "center",
770
770
  background = "solid",
@@ -801,7 +801,7 @@ function baseLayout(content, options) {
801
801
  </style>` : "";
802
802
  const sizeClass = getSizeClass(size);
803
803
  const alignmentClasses = getAlignmentClasses(alignment);
804
- const backgroundClasses = getBackgroundClasses(background, theme);
804
+ const backgroundClasses = getBackgroundClasses(background);
805
805
  const allBodyClasses = [backgroundClasses, "font-sans antialiased", bodyClass].filter(Boolean).join(" ");
806
806
  const metaTags = buildMetaTags(options);
807
807
  const bodyAttrStr = buildBodyAttrs(bodyAttrs);
@@ -2668,926 +2668,6 @@ var errorLayoutBuilder = createLayoutBuilder({
2668
2668
  background: "solid"
2669
2669
  });
2670
2670
 
2671
- // libs/ui/src/pages/consent.ts
2672
- function consentPage(options) {
2673
- const {
2674
- client,
2675
- user,
2676
- permissions,
2677
- approveUrl,
2678
- denyUrl,
2679
- csrfToken,
2680
- state,
2681
- redirectUri,
2682
- responseType,
2683
- nonce,
2684
- codeChallenge,
2685
- codeChallengeMethod,
2686
- error,
2687
- layout = {},
2688
- warningMessage,
2689
- allowScopeSelection = false,
2690
- approveText = "Allow",
2691
- denyText = "Deny"
2692
- } = options;
2693
- const errorAlert = error ? alert(error, { variant: "danger", dismissible: true }) : "";
2694
- const unverifiedWarning = !client.verified ? alert(warningMessage || "This application has not been verified. Only authorize applications you trust.", {
2695
- variant: "warning",
2696
- title: "Unverified Application"
2697
- }) : "";
2698
- const clientHeader = `
2699
- <div class="text-center mb-6">
2700
- ${client.icon ? `<img src="${escapeHtml2(client.icon)}" alt="${escapeHtml2(
2701
- client.name
2702
- )}" class="w-16 h-16 rounded-xl mx-auto mb-4 shadow-md">` : `<div class="inline-flex items-center justify-center w-16 h-16 rounded-xl bg-gradient-to-br from-primary to-secondary text-white font-bold text-2xl mx-auto mb-4 shadow-md">
2703
- ${escapeHtml2(client.name.charAt(0).toUpperCase())}
2704
- </div>`}
2705
- <h1 class="text-xl font-bold text-text-primary">
2706
- ${client.verified ? `<span class="inline-flex items-center gap-1">
2707
- ${escapeHtml2(client.name)}
2708
- <svg class="w-5 h-5 text-primary" fill="currentColor" viewBox="0 0 20 20">
2709
- <path fill-rule="evenodd" d="M6.267 3.455a3.066 3.066 0 001.745-.723 3.066 3.066 0 013.976 0 3.066 3.066 0 001.745.723 3.066 3.066 0 012.812 2.812c.051.643.304 1.254.723 1.745a3.066 3.066 0 010 3.976 3.066 3.066 0 00-.723 1.745 3.066 3.066 0 01-2.812 2.812 3.066 3.066 0 00-1.745.723 3.066 3.066 0 01-3.976 0 3.066 3.066 0 00-1.745-.723 3.066 3.066 0 01-2.812-2.812 3.066 3.066 0 00-.723-1.745 3.066 3.066 0 010-3.976 3.066 3.066 0 00.723-1.745 3.066 3.066 0 012.812-2.812zm7.44 5.252a1 1 0 00-1.414-1.414L9 10.586 7.707 9.293a1 1 0 00-1.414 1.414l2 2a1 1 0 001.414 0l4-4z" clip-rule="evenodd"/>
2710
- </svg>
2711
- </span>` : escapeHtml2(client.name)}
2712
- </h1>
2713
- <p class="text-text-secondary mt-1">wants to access your account</p>
2714
- </div>
2715
- `;
2716
- const userSection = user ? `
2717
- <div class="flex items-center gap-3 p-4 bg-gray-50 rounded-lg mb-6">
2718
- ${user.avatar ? `<img src="${escapeHtml2(user.avatar)}" class="w-12 h-12 rounded-full">` : `<div class="w-12 h-12 rounded-full bg-primary text-white flex items-center justify-center font-semibold text-lg">
2719
- ${escapeHtml2((user.name || user.email || "U").charAt(0).toUpperCase())}
2720
- </div>`}
2721
- <div class="flex-1 min-w-0">
2722
- ${user.name ? `<div class="font-medium text-text-primary truncate">${escapeHtml2(user.name)}</div>` : ""}
2723
- ${user.email ? `<div class="text-sm text-text-secondary truncate">${escapeHtml2(user.email)}</div>` : ""}
2724
- </div>
2725
- <a href="/login?prompt=select_account" class="text-sm text-primary hover:text-primary/80">
2726
- Switch account
2727
- </a>
2728
- </div>
2729
- ` : "";
2730
- const permissionsSection = `
2731
- <div class="mb-6">
2732
- <h3 class="font-medium text-text-primary mb-3">This will allow ${escapeHtml2(client.name)} to:</h3>
2733
- ${permissionList(permissions, {
2734
- checkable: allowScopeSelection,
2735
- inputName: "scope"
2736
- })}
2737
- </div>
2738
- `;
2739
- const hiddenFields = [
2740
- csrfToken ? csrfInput(csrfToken) : "",
2741
- state ? hiddenInput("state", state) : "",
2742
- redirectUri ? hiddenInput("redirect_uri", redirectUri) : "",
2743
- responseType ? hiddenInput("response_type", responseType) : "",
2744
- nonce ? hiddenInput("nonce", nonce) : "",
2745
- codeChallenge ? hiddenInput("code_challenge", codeChallenge) : "",
2746
- codeChallengeMethod ? hiddenInput("code_challenge_method", codeChallengeMethod) : "",
2747
- hiddenInput("client_id", client.clientId),
2748
- // Include all scopes if not selectable
2749
- !allowScopeSelection ? permissions.map((p) => hiddenInput("scope[]", p.scope)).join("\n") : ""
2750
- ].filter(Boolean).join("\n");
2751
- const actionsHtml = `
2752
- <div class="flex gap-3 pt-4">
2753
- <form action="${escapeHtml2(denyUrl || approveUrl)}" method="post" class="flex-1">
2754
- ${hiddenFields}
2755
- <input type="hidden" name="action" value="deny">
2756
- ${outlineButton(denyText, { type: "submit", fullWidth: true })}
2757
- </form>
2758
- <form action="${escapeHtml2(approveUrl)}" method="post" class="flex-1">
2759
- ${hiddenFields}
2760
- <input type="hidden" name="action" value="approve">
2761
- ${primaryButton(approveText, { type: "submit", fullWidth: true })}
2762
- </form>
2763
- </div>
2764
- `;
2765
- const linksHtml = client.privacyUrl || client.termsUrl || client.websiteUrl ? `
2766
- <div class="text-center text-xs text-text-secondary mt-6 space-x-3">
2767
- ${client.websiteUrl ? `<a href="${escapeHtml2(
2768
- client.websiteUrl
2769
- )}" target="_blank" rel="noopener" class="hover:text-primary">Website</a>` : ""}
2770
- ${client.privacyUrl ? `<a href="${escapeHtml2(
2771
- client.privacyUrl
2772
- )}" target="_blank" rel="noopener" class="hover:text-primary">Privacy Policy</a>` : ""}
2773
- ${client.termsUrl ? `<a href="${escapeHtml2(
2774
- client.termsUrl
2775
- )}" target="_blank" rel="noopener" class="hover:text-primary">Terms of Service</a>` : ""}
2776
- </div>
2777
- ` : "";
2778
- const content = `
2779
- ${errorAlert}
2780
- ${unverifiedWarning}
2781
- ${clientHeader}
2782
- ${userSection}
2783
- ${permissionsSection}
2784
- ${actionsHtml}
2785
- ${linksHtml}
2786
- `;
2787
- return consentLayout(content, {
2788
- title: `Authorize ${client.name}`,
2789
- clientName: client.name,
2790
- clientIcon: client.icon,
2791
- userInfo: user,
2792
- ...layout
2793
- });
2794
- }
2795
- function consentSuccessPage(options) {
2796
- const { client, redirectUrl, autoRedirectDelay = 3e3, layout = {} } = options;
2797
- const redirectScript = redirectUrl && autoRedirectDelay > 0 ? `
2798
- <script>
2799
- setTimeout(() => {
2800
- window.location.href = '${escapeHtml2(redirectUrl)}';
2801
- }, ${autoRedirectDelay});
2802
- </script>
2803
- ` : "";
2804
- const content = `
2805
- <div class="text-center">
2806
- <div class="inline-flex items-center justify-center w-16 h-16 rounded-full bg-success/10 mb-6">
2807
- <svg class="w-8 h-8 text-success" fill="none" stroke="currentColor" viewBox="0 0 24 24">
2808
- <path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M5 13l4 4L19 7"/>
2809
- </svg>
2810
- </div>
2811
- <h1 class="text-2xl font-bold text-text-primary mb-2">Authorization Successful</h1>
2812
- <p class="text-text-secondary mb-4">
2813
- You have authorized <strong>${escapeHtml2(client.name)}</strong> to access your account.
2814
- </p>
2815
- ${redirectUrl ? `<p class="text-sm text-text-secondary">Redirecting you back to ${escapeHtml2(client.name)}...</p>` : ""}
2816
- </div>
2817
- ${redirectScript}
2818
- `;
2819
- return consentLayout(content, {
2820
- title: "Authorization Successful",
2821
- clientName: client.name,
2822
- clientIcon: client.icon,
2823
- ...layout
2824
- });
2825
- }
2826
- function consentDeniedPage(options) {
2827
- const { client, redirectUrl, layout = {} } = options;
2828
- const content = `
2829
- <div class="text-center">
2830
- <div class="inline-flex items-center justify-center w-16 h-16 rounded-full bg-gray-100 mb-6">
2831
- <svg class="w-8 h-8 text-gray-500" fill="none" stroke="currentColor" viewBox="0 0 24 24">
2832
- <path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M6 18L18 6M6 6l12 12"/>
2833
- </svg>
2834
- </div>
2835
- <h1 class="text-2xl font-bold text-text-primary mb-2">Authorization Denied</h1>
2836
- <p class="text-text-secondary mb-6">
2837
- You denied <strong>${escapeHtml2(client.name)}</strong> access to your account.
2838
- </p>
2839
- ${redirectUrl ? `
2840
- <a href="${escapeHtml2(
2841
- redirectUrl
2842
- )}" class="inline-block px-6 py-3 bg-primary hover:bg-primary/90 text-white font-medium rounded-lg transition-colors">
2843
- Return to ${escapeHtml2(client.name)}
2844
- </a>
2845
- ` : ""}
2846
- </div>
2847
- `;
2848
- return consentLayout(content, {
2849
- title: "Authorization Denied",
2850
- clientName: client.name,
2851
- clientIcon: client.icon,
2852
- ...layout
2853
- });
2854
- }
2855
-
2856
- // libs/ui/src/pages/error.ts
2857
- function errorPage(options) {
2858
- const {
2859
- code,
2860
- title = "Something went wrong",
2861
- message,
2862
- details,
2863
- showStack = false,
2864
- stack,
2865
- showRetry = true,
2866
- retryUrl,
2867
- showHome = true,
2868
- homeUrl = "/",
2869
- showBack = false,
2870
- actions,
2871
- layout = {},
2872
- requestId
2873
- } = options;
2874
- const detailsHtml = details || showStack && stack ? `
2875
- <div class="mt-8 text-left">
2876
- ${details ? `
2877
- <div class="p-4 bg-gray-50 rounded-lg text-sm text-text-secondary mb-4">
2878
- <strong class="text-text-primary">Details:</strong>
2879
- <p class="mt-1">${escapeHtml2(details)}</p>
2880
- </div>
2881
- ` : ""}
2882
- ${showStack && stack ? `
2883
- <details class="p-4 bg-gray-900 rounded-lg text-sm">
2884
- <summary class="text-gray-300 cursor-pointer hover:text-white">Stack Trace</summary>
2885
- <pre class="mt-2 text-xs text-gray-400 overflow-x-auto whitespace-pre-wrap">${escapeHtml2(stack)}</pre>
2886
- </details>
2887
- ` : ""}
2888
- </div>
2889
- ` : "";
2890
- const requestIdHtml = requestId ? `
2891
- <p class="text-xs text-text-secondary mt-6">
2892
- Request ID: <code class="px-1.5 py-0.5 bg-gray-100 rounded text-xs">${escapeHtml2(requestId)}</code>
2893
- </p>
2894
- ` : "";
2895
- const content = `
2896
- ${detailsHtml}
2897
- ${actions || ""}
2898
- ${requestIdHtml}
2899
- `;
2900
- return errorLayout(content, {
2901
- title: `${code ? `Error ${code} - ` : ""}${title}`,
2902
- errorCode: code?.toString(),
2903
- errorTitle: title,
2904
- errorMessage: message,
2905
- showRetry,
2906
- retryUrl,
2907
- showHome,
2908
- homeUrl,
2909
- ...layout
2910
- });
2911
- }
2912
- function notFoundPage(options = {}) {
2913
- return errorPage({
2914
- code: 404,
2915
- title: "Page Not Found",
2916
- message: "The page you're looking for doesn't exist or has been moved.",
2917
- showRetry: false,
2918
- ...options
2919
- });
2920
- }
2921
- function forbiddenPage(options = {}) {
2922
- return errorPage({
2923
- code: 403,
2924
- title: "Access Denied",
2925
- message: "You don't have permission to access this resource.",
2926
- showRetry: false,
2927
- ...options
2928
- });
2929
- }
2930
- function unauthorizedPage(options = {}) {
2931
- const { loginUrl = "/login", ...rest } = options;
2932
- return errorPage({
2933
- code: 401,
2934
- title: "Authentication Required",
2935
- message: "Please sign in to access this resource.",
2936
- showRetry: false,
2937
- showHome: false,
2938
- actions: `
2939
- <div class="flex justify-center mt-8">
2940
- <a href="${escapeHtml2(
2941
- loginUrl
2942
- )}" class="px-6 py-3 bg-primary hover:bg-primary/90 text-white font-medium rounded-lg transition-colors">
2943
- Sign In
2944
- </a>
2945
- </div>
2946
- `,
2947
- ...rest
2948
- });
2949
- }
2950
- function serverErrorPage(options = {}) {
2951
- return errorPage({
2952
- code: 500,
2953
- title: "Server Error",
2954
- message: "We're having trouble processing your request. Please try again later.",
2955
- showRetry: true,
2956
- ...options
2957
- });
2958
- }
2959
- function maintenancePage(options = {}) {
2960
- const { estimatedTime, ...rest } = options;
2961
- const timeMessage = estimatedTime ? `We expect to be back by ${escapeHtml2(estimatedTime)}.` : "We'll be back shortly.";
2962
- return errorPage({
2963
- code: 503,
2964
- title: "Under Maintenance",
2965
- message: `We're currently performing scheduled maintenance. ${timeMessage}`,
2966
- showRetry: true,
2967
- showHome: false,
2968
- ...rest
2969
- });
2970
- }
2971
- function rateLimitPage(options = {}) {
2972
- const { retryAfter, ...rest } = options;
2973
- const retryMessage = retryAfter ? `Please wait ${retryAfter} seconds before trying again.` : "Please wait a moment before trying again.";
2974
- return errorPage({
2975
- code: 429,
2976
- title: "Too Many Requests",
2977
- message: `You've made too many requests. ${retryMessage}`,
2978
- showRetry: true,
2979
- showHome: true,
2980
- ...rest
2981
- });
2982
- }
2983
- function offlinePage(options = {}) {
2984
- return errorPage({
2985
- title: "You're Offline",
2986
- message: "Please check your internet connection and try again.",
2987
- showRetry: true,
2988
- showHome: false,
2989
- ...options,
2990
- layout: {
2991
- ...options.layout
2992
- }
2993
- });
2994
- }
2995
- function sessionExpiredPage(options = {}) {
2996
- const { loginUrl = "/login", ...rest } = options;
2997
- return errorPage({
2998
- title: "Session Expired",
2999
- message: "Your session has expired. Please sign in again to continue.",
3000
- showRetry: false,
3001
- showHome: false,
3002
- actions: `
3003
- <div class="flex justify-center mt-8">
3004
- <a href="${escapeHtml2(
3005
- loginUrl
3006
- )}" class="px-6 py-3 bg-primary hover:bg-primary/90 text-white font-medium rounded-lg transition-colors">
3007
- Sign In Again
3008
- </a>
3009
- </div>
3010
- `,
3011
- ...rest
3012
- });
3013
- }
3014
- function oauthErrorPage(options) {
3015
- const { errorCode, errorDescription, redirectUri, clientName, ...rest } = options;
3016
- const errorMessages = {
3017
- invalid_request: {
3018
- title: "Invalid Request",
3019
- message: "The authorization request is missing required parameters or is malformed."
3020
- },
3021
- unauthorized_client: {
3022
- title: "Unauthorized Client",
3023
- message: "The client is not authorized to request an authorization code."
3024
- },
3025
- access_denied: {
3026
- title: "Access Denied",
3027
- message: "The resource owner denied the authorization request."
3028
- },
3029
- unsupported_response_type: {
3030
- title: "Unsupported Response Type",
3031
- message: "The authorization server does not support the requested response type."
3032
- },
3033
- invalid_scope: {
3034
- title: "Invalid Scope",
3035
- message: "The requested scope is invalid, unknown, or malformed."
3036
- },
3037
- server_error: {
3038
- title: "Server Error",
3039
- message: "The authorization server encountered an unexpected error."
3040
- },
3041
- temporarily_unavailable: {
3042
- title: "Temporarily Unavailable",
3043
- message: "The authorization server is temporarily unavailable. Please try again later."
3044
- }
3045
- };
3046
- const errorInfo = errorCode && errorMessages[errorCode] ? errorMessages[errorCode] : { title: "Authorization Error", message: errorDescription || "An error occurred during authorization." };
3047
- const clientMessage = clientName ? ` while connecting to ${escapeHtml2(clientName)}` : "";
3048
- const redirectAction = redirectUri ? `
3049
- <a href="${escapeHtml2(
3050
- redirectUri
3051
- )}" class="px-6 py-3 bg-gray-100 hover:bg-gray-200 text-text-primary font-medium rounded-lg transition-colors">
3052
- Return to ${clientName ? escapeHtml2(clientName) : "Application"}
3053
- </a>
3054
- ` : "";
3055
- return errorPage({
3056
- title: errorInfo.title,
3057
- message: `${errorInfo.message}${clientMessage}`,
3058
- details: errorCode && errorDescription ? `Error: ${errorCode}
3059
- ${errorDescription}` : void 0,
3060
- showRetry: errorCode === "server_error" || errorCode === "temporarily_unavailable",
3061
- showHome: true,
3062
- actions: redirectAction ? `<div class="flex justify-center gap-4 mt-8">${redirectAction}</div>` : void 0,
3063
- ...rest
3064
- });
3065
- }
3066
-
3067
- // libs/ui/src/widgets/resource.ts
3068
- var resourceIcons = {
3069
- document: `<svg class="w-8 h-8" fill="none" stroke="currentColor" viewBox="0 0 24 24">
3070
- <path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M9 12h6m-6 4h6m2 5H7a2 2 0 01-2-2V5a2 2 0 012-2h5.586a1 1 0 01.707.293l5.414 5.414a1 1 0 01.293.707V19a2 2 0 01-2 2z"/>
3071
- </svg>`,
3072
- image: `<svg class="w-8 h-8" fill="none" stroke="currentColor" viewBox="0 0 24 24">
3073
- <path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M4 16l4.586-4.586a2 2 0 012.828 0L16 16m-2-2l1.586-1.586a2 2 0 012.828 0L20 14m-6-6h.01M6 20h12a2 2 0 002-2V6a2 2 0 00-2-2H6a2 2 0 00-2 2v12a2 2 0 002 2z"/>
3074
- </svg>`,
3075
- code: `<svg class="w-8 h-8" fill="none" stroke="currentColor" viewBox="0 0 24 24">
3076
- <path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M10 20l4-16m4 4l4 4-4 4M6 16l-4-4 4-4"/>
3077
- </svg>`,
3078
- data: `<svg class="w-8 h-8" fill="none" stroke="currentColor" viewBox="0 0 24 24">
3079
- <path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M4 7v10c0 2.21 3.582 4 8 4s8-1.79 8-4V7M4 7c0 2.21 3.582 4 8 4s8-1.79 8-4M4 7c0-2.21 3.582-4 8-4s8 1.79 8 4"/>
3080
- </svg>`,
3081
- file: `<svg class="w-8 h-8" fill="none" stroke="currentColor" viewBox="0 0 24 24">
3082
- <path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M7 21h10a2 2 0 002-2V9.414a1 1 0 00-.293-.707l-5.414-5.414A1 1 0 0012.586 3H7a2 2 0 00-2 2v14a2 2 0 002 2z"/>
3083
- </svg>`,
3084
- link: `<svg class="w-8 h-8" fill="none" stroke="currentColor" viewBox="0 0 24 24">
3085
- <path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M13.828 10.172a4 4 0 00-5.656 0l-4 4a4 4 0 105.656 5.656l1.102-1.101m-.758-4.899a4 4 0 005.656 0l4-4a4 4 0 00-5.656-5.656l-1.1 1.1"/>
3086
- </svg>`,
3087
- user: `<svg class="w-8 h-8" fill="none" stroke="currentColor" viewBox="0 0 24 24">
3088
- <path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M16 7a4 4 0 11-8 0 4 4 0 018 0zM12 14a7 7 0 00-7 7h14a7 7 0 00-7-7z"/>
3089
- </svg>`,
3090
- event: `<svg class="w-8 h-8" fill="none" stroke="currentColor" viewBox="0 0 24 24">
3091
- <path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M8 7V3m8 4V3m-9 8h10M5 21h14a2 2 0 002-2V7a2 2 0 00-2-2H5a2 2 0 00-2 2v12a2 2 0 002 2z"/>
3092
- </svg>`,
3093
- message: `<svg class="w-8 h-8" fill="none" stroke="currentColor" viewBox="0 0 24 24">
3094
- <path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M8 10h.01M12 10h.01M16 10h.01M9 16H5a2 2 0 01-2-2V6a2 2 0 012-2h14a2 2 0 012 2v8a2 2 0 01-2 2h-5l-5 5v-5z"/>
3095
- </svg>`,
3096
- task: `<svg class="w-8 h-8" fill="none" stroke="currentColor" viewBox="0 0 24 24">
3097
- <path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M9 5H7a2 2 0 00-2 2v12a2 2 0 002 2h10a2 2 0 002-2V7a2 2 0 00-2-2h-2M9 5a2 2 0 002 2h2a2 2 0 002-2M9 5a2 2 0 012-2h2a2 2 0 012 2m-6 9l2 2 4-4"/>
3098
- </svg>`,
3099
- custom: `<svg class="w-8 h-8" fill="none" stroke="currentColor" viewBox="0 0 24 24">
3100
- <path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M19 11H5m14 0a2 2 0 012 2v6a2 2 0 01-2 2H5a2 2 0 01-2-2v-6a2 2 0 012-2m14 0V9a2 2 0 00-2-2M5 11V9a2 2 0 012-2m0 0V5a2 2 0 012-2h6a2 2 0 012 2v2M7 7h10"/>
3101
- </svg>`
3102
- };
3103
- function formatFileSize(bytes) {
3104
- if (bytes === 0) return "0 B";
3105
- const k = 1024;
3106
- const sizes = ["B", "KB", "MB", "GB", "TB"];
3107
- const i = Math.floor(Math.log(bytes) / Math.log(k));
3108
- return `${parseFloat((bytes / Math.pow(k, i)).toFixed(1))} ${sizes[i]}`;
3109
- }
3110
- function formatDate(date) {
3111
- const d = typeof date === "string" ? new Date(date) : date;
3112
- return d.toLocaleDateString("en-US", {
3113
- year: "numeric",
3114
- month: "short",
3115
- day: "numeric"
3116
- });
3117
- }
3118
- function resourceWidget(options) {
3119
- const {
3120
- type,
3121
- title,
3122
- description,
3123
- icon,
3124
- thumbnail,
3125
- url,
3126
- meta,
3127
- status,
3128
- actions = [],
3129
- className = "",
3130
- cardOptions = {}
3131
- } = options;
3132
- const iconHtml = thumbnail ? `<div class="w-16 h-16 rounded-lg overflow-hidden bg-gray-100 flex-shrink-0">
3133
- <img src="${escapeHtml2(thumbnail)}" alt="${escapeHtml2(title)}" class="w-full h-full object-cover">
3134
- </div>` : `<div class="w-16 h-16 rounded-lg bg-gray-100 flex items-center justify-center flex-shrink-0 text-gray-400">
3135
- ${icon || resourceIcons[type]}
3136
- </div>`;
3137
- const statusHtml = status ? badge(status.label, { variant: status.variant, size: "sm" }) : "";
3138
- const metaItems = [];
3139
- if (meta?.size) {
3140
- metaItems.push(formatFileSize(meta.size));
3141
- }
3142
- if (meta?.mimeType) {
3143
- metaItems.push(meta.mimeType);
3144
- }
3145
- if (meta?.updatedAt) {
3146
- metaItems.push(`Updated ${formatDate(meta.updatedAt)}`);
3147
- } else if (meta?.createdAt) {
3148
- metaItems.push(`Created ${formatDate(meta.createdAt)}`);
3149
- }
3150
- if (meta?.author) {
3151
- metaItems.push(`by ${meta.author}`);
3152
- }
3153
- const metaHtml = metaItems.length > 0 ? `<div class="text-xs text-text-secondary mt-1">${metaItems.join(" \u2022 ")}</div>` : "";
3154
- const tagsHtml = meta?.tags && meta.tags.length > 0 ? `<div class="flex flex-wrap gap-1 mt-2">
3155
- ${meta.tags.map((tag) => badge(tag, { variant: "default", size: "sm" })).join("")}
3156
- </div>` : "";
3157
- const actionsHtml = actions.length > 0 ? `<div class="flex gap-2 mt-4">
3158
- ${actions.map((action) => {
3159
- const variantMap = {
3160
- primary: "primary",
3161
- secondary: "secondary",
3162
- danger: "danger",
3163
- ghost: "ghost"
3164
- };
3165
- const variant = action.variant ? variantMap[action.variant] : "ghost";
3166
- const htmxAttrs = [];
3167
- if (action.htmx) {
3168
- if (action.htmx.get) htmxAttrs.push(`hx-get="${escapeHtml2(action.htmx.get)}"`);
3169
- if (action.htmx.post) htmxAttrs.push(`hx-post="${escapeHtml2(action.htmx.post)}"`);
3170
- if (action.htmx.delete) htmxAttrs.push(`hx-delete="${escapeHtml2(action.htmx.delete)}"`);
3171
- if (action.htmx.target) htmxAttrs.push(`hx-target="${escapeHtml2(action.htmx.target)}"`);
3172
- if (action.htmx.swap) htmxAttrs.push(`hx-swap="${escapeHtml2(action.htmx.swap)}"`);
3173
- if (action.htmx.confirm) htmxAttrs.push(`hx-confirm="${escapeHtml2(action.htmx.confirm)}"`);
3174
- }
3175
- return button(action.label, {
3176
- variant,
3177
- size: "sm",
3178
- href: action.href,
3179
- disabled: action.disabled,
3180
- iconBefore: action.icon
3181
- });
3182
- }).join("")}
3183
- </div>` : "";
3184
- const content = `
3185
- <div class="flex gap-4">
3186
- ${iconHtml}
3187
- <div class="flex-1 min-w-0">
3188
- <div class="flex items-start justify-between gap-2">
3189
- <div class="min-w-0">
3190
- ${url ? `<a href="${escapeHtml2(
3191
- url
3192
- )}" class="font-semibold text-text-primary hover:text-primary truncate block">${escapeHtml2(
3193
- title
3194
- )}</a>` : `<h3 class="font-semibold text-text-primary truncate">${escapeHtml2(title)}</h3>`}
3195
- ${description ? `<p class="text-sm text-text-secondary mt-0.5 line-clamp-2">${escapeHtml2(description)}</p>` : ""}
3196
- ${metaHtml}
3197
- ${tagsHtml}
3198
- </div>
3199
- ${statusHtml}
3200
- </div>
3201
- ${actionsHtml}
3202
- </div>
3203
- </div>
3204
- `;
3205
- return card(content, {
3206
- variant: "default",
3207
- size: "md",
3208
- className: `resource-widget resource-${type} ${className}`,
3209
- ...cardOptions
3210
- });
3211
- }
3212
- function resourceList(options) {
3213
- const {
3214
- resources,
3215
- title,
3216
- emptyMessage = "No resources found",
3217
- layout = "list",
3218
- columns = 2,
3219
- className = "",
3220
- showLoadMore = false,
3221
- loadMoreUrl
3222
- } = options;
3223
- const titleHtml = title ? `<h2 class="text-lg font-semibold text-text-primary mb-4">${escapeHtml2(title)}</h2>` : "";
3224
- if (resources.length === 0) {
3225
- return `<div class="${className}">
3226
- ${titleHtml}
3227
- <div class="text-center py-12 text-text-secondary">
3228
- <svg class="w-12 h-12 mx-auto mb-4 text-gray-300" fill="none" stroke="currentColor" viewBox="0 0 24 24">
3229
- <path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M20 13V6a2 2 0 00-2-2H6a2 2 0 00-2 2v7m16 0v5a2 2 0 01-2 2H6a2 2 0 01-2-2v-5m16 0h-2.586a1 1 0 00-.707.293l-2.414 2.414a1 1 0 01-.707.293h-3.172a1 1 0 01-.707-.293l-2.414-2.414A1 1 0 006.586 13H4"/>
3230
- </svg>
3231
- <p>${escapeHtml2(emptyMessage)}</p>
3232
- </div>
3233
- </div>`;
3234
- }
3235
- const layoutClasses = layout === "grid" ? `grid grid-cols-1 md:grid-cols-${columns} gap-4` : "space-y-4";
3236
- const resourcesHtml = resources.map((r) => resourceWidget(r)).join("\n");
3237
- const loadMoreHtml = showLoadMore && loadMoreUrl ? `<div class="text-center mt-6">
3238
- ${button("Load More", {
3239
- variant: "outline",
3240
- href: loadMoreUrl
3241
- })}
3242
- </div>` : "";
3243
- return `<div class="resource-list ${className}">
3244
- ${titleHtml}
3245
- <div class="${layoutClasses}">
3246
- ${resourcesHtml}
3247
- </div>
3248
- ${loadMoreHtml}
3249
- </div>`;
3250
- }
3251
- function resourceItem(options) {
3252
- const { type, title, description, icon, url, meta, status } = options;
3253
- const iconHtml = `<div class="w-10 h-10 rounded-lg bg-gray-100 flex items-center justify-center flex-shrink-0 text-gray-400">
3254
- ${icon || resourceIcons[type]}
3255
- </div>`;
3256
- const statusHtml = status ? badge(status.label, { variant: status.variant, size: "sm" }) : "";
3257
- const metaText = meta?.size ? formatFileSize(meta.size) : "";
3258
- const titleElement = url ? `<a href="${escapeHtml2(url)}" class="font-medium text-text-primary hover:text-primary">${escapeHtml2(title)}</a>` : `<span class="font-medium text-text-primary">${escapeHtml2(title)}</span>`;
3259
- return `<div class="flex items-center gap-3 p-3 rounded-lg hover:bg-gray-50 transition-colors">
3260
- ${iconHtml}
3261
- <div class="flex-1 min-w-0">
3262
- <div class="flex items-center gap-2">
3263
- ${titleElement}
3264
- ${statusHtml}
3265
- </div>
3266
- ${description || metaText ? `<p class="text-sm text-text-secondary truncate">${escapeHtml2(description || metaText)}</p>` : ""}
3267
- </div>
3268
- </div>`;
3269
- }
3270
- function codePreview(options) {
3271
- const {
3272
- code,
3273
- language = "text",
3274
- filename,
3275
- lineNumbers = true,
3276
- maxHeight = "400px",
3277
- showCopy = true,
3278
- className = ""
3279
- } = options;
3280
- const lines = code.split("\n");
3281
- const lineNumbersHtml = lineNumbers ? `<div class="text-right select-none pr-4 text-gray-500">
3282
- ${lines.map((_, i) => `<div>${i + 1}</div>`).join("")}
3283
- </div>` : "";
3284
- const copyScript = showCopy ? `<script>
3285
- function copyCode(btn, code) {
3286
- navigator.clipboard.writeText(code).then(() => {
3287
- const original = btn.innerHTML;
3288
- btn.innerHTML = '<svg class="w-4 h-4 text-success" fill="none" stroke="currentColor" viewBox="0 0 24 24"><path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M5 13l4 4L19 7"/></svg>';
3289
- setTimeout(() => btn.innerHTML = original, 2000);
3290
- });
3291
- }
3292
- </script>` : "";
3293
- const copyButton = showCopy ? `<button
3294
- type="button"
3295
- onclick="copyCode(this, ${escapeHtml2(JSON.stringify(code))})"
3296
- class="p-1.5 rounded hover:bg-gray-700 transition-colors"
3297
- title="Copy code"
3298
- >
3299
- <svg class="w-4 h-4" fill="none" stroke="currentColor" viewBox="0 0 24 24">
3300
- <path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M8 16H6a2 2 0 01-2-2V6a2 2 0 012-2h8a2 2 0 012 2v2m-6 12h8a2 2 0 002-2v-8a2 2 0 00-2-2h-8a2 2 0 00-2 2v8a2 2 0 002 2z"/>
3301
- </svg>
3302
- </button>` : "";
3303
- return `<div class="code-preview rounded-lg overflow-hidden ${className}">
3304
- ${filename || showCopy ? `
3305
- <div class="flex items-center justify-between px-4 py-2 bg-gray-800 border-b border-gray-700">
3306
- ${filename ? `<span class="text-sm text-gray-300">${escapeHtml2(filename)}</span>` : "<span></span>"}
3307
- <div class="flex items-center gap-2">
3308
- ${language ? `<span class="text-xs text-gray-500">${escapeHtml2(language)}</span>` : ""}
3309
- ${copyButton}
3310
- </div>
3311
- </div>
3312
- ` : ""}
3313
- <div class="bg-gray-900 p-4 overflow-auto" style="max-height: ${maxHeight}">
3314
- <div class="flex text-sm font-mono">
3315
- ${lineNumbersHtml}
3316
- <pre class="flex-1 text-gray-100"><code>${escapeHtml2(code)}</code></pre>
3317
- </div>
3318
- </div>
3319
- ${copyScript}
3320
- </div>`;
3321
- }
3322
- function imagePreview(options) {
3323
- const { src, alt, caption, maxHeight = "400px", clickable = true, className = "" } = options;
3324
- const imageHtml = `<img
3325
- src="${escapeHtml2(src)}"
3326
- alt="${escapeHtml2(alt)}"
3327
- class="max-w-full h-auto rounded-lg"
3328
- style="max-height: ${maxHeight}"
3329
- >`;
3330
- const captionHtml = caption ? `<p class="text-sm text-text-secondary mt-2 text-center">${escapeHtml2(caption)}</p>` : "";
3331
- const content = clickable ? `<a href="${escapeHtml2(src)}" target="_blank" rel="noopener" class="block">${imageHtml}</a>` : imageHtml;
3332
- return `<div class="image-preview ${className}">
3333
- ${content}
3334
- ${captionHtml}
3335
- </div>`;
3336
- }
3337
-
3338
- // libs/ui/src/widgets/progress.ts
3339
- function progressBar(options) {
3340
- const {
3341
- value,
3342
- showLabel = true,
3343
- labelPosition = "outside",
3344
- size = "md",
3345
- variant = "primary",
3346
- animated = false,
3347
- className = "",
3348
- label
3349
- } = options;
3350
- const clampedValue = Math.min(100, Math.max(0, value));
3351
- const sizeClasses = {
3352
- sm: "h-1.5",
3353
- md: "h-2.5",
3354
- lg: "h-4"
3355
- };
3356
- const variantClasses = {
3357
- primary: "bg-primary",
3358
- success: "bg-success",
3359
- warning: "bg-warning",
3360
- danger: "bg-danger",
3361
- info: "bg-blue-500"
3362
- };
3363
- const animatedClass = animated ? "bg-stripes animate-stripes" : "";
3364
- const displayLabel = label || `${Math.round(clampedValue)}%`;
3365
- const insideLabel = labelPosition === "inside" && size === "lg" && clampedValue > 10 ? `<span class="absolute inset-0 flex items-center justify-center text-xs font-medium text-white">${escapeHtml2(
3366
- displayLabel
3367
- )}</span>` : "";
3368
- const outsideLabel = showLabel && labelPosition === "outside" ? `<div class="flex justify-between mb-1">
3369
- <span class="text-sm font-medium text-text-primary">${label ? escapeHtml2(label) : "Progress"}</span>
3370
- <span class="text-sm text-text-secondary">${Math.round(clampedValue)}%</span>
3371
- </div>` : "";
3372
- return `<div class="progress-bar ${className}">
3373
- ${outsideLabel}
3374
- <div class="relative w-full ${sizeClasses[size]} bg-gray-200 rounded-full overflow-hidden">
3375
- <div
3376
- class="${variantClasses[variant]} ${sizeClasses[size]} ${animatedClass} rounded-full transition-all duration-300"
3377
- style="width: ${clampedValue}%"
3378
- role="progressbar"
3379
- aria-valuenow="${clampedValue}"
3380
- aria-valuemin="0"
3381
- aria-valuemax="100"
3382
- ></div>
3383
- ${insideLabel}
3384
- </div>
3385
- </div>
3386
- ${animated ? `<style>
3387
- .bg-stripes {
3388
- background-image: linear-gradient(
3389
- 45deg,
3390
- rgba(255,255,255,0.15) 25%,
3391
- transparent 25%,
3392
- transparent 50%,
3393
- rgba(255,255,255,0.15) 50%,
3394
- rgba(255,255,255,0.15) 75%,
3395
- transparent 75%,
3396
- transparent
3397
- );
3398
- background-size: 1rem 1rem;
3399
- }
3400
- @keyframes stripes {
3401
- from { background-position: 1rem 0; }
3402
- to { background-position: 0 0; }
3403
- }
3404
- .animate-stripes {
3405
- animation: stripes 1s linear infinite;
3406
- }
3407
- </style>` : ""}`;
3408
- }
3409
- function stepProgress(options) {
3410
- const { steps, orientation = "horizontal", connector = "line", className = "" } = options;
3411
- const getStepIcon = (step, index) => {
3412
- if (step.icon) return step.icon;
3413
- if (step.status === "completed") {
3414
- return `<svg class="w-5 h-5" fill="none" stroke="currentColor" viewBox="0 0 24 24">
3415
- <path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M5 13l4 4L19 7"/>
3416
- </svg>`;
3417
- }
3418
- return `<span class="font-medium">${index + 1}</span>`;
3419
- };
3420
- const getStepClasses = (status) => {
3421
- switch (status) {
3422
- case "completed":
3423
- return {
3424
- circle: "bg-success text-white",
3425
- text: "text-text-primary"
3426
- };
3427
- case "current":
3428
- return {
3429
- circle: "bg-primary text-white ring-4 ring-primary/20",
3430
- text: "text-primary font-medium"
3431
- };
3432
- case "upcoming":
3433
- default:
3434
- return {
3435
- circle: "bg-gray-200 text-gray-500",
3436
- text: "text-text-secondary"
3437
- };
3438
- }
3439
- };
3440
- if (orientation === "vertical") {
3441
- const stepsHtml2 = steps.map((step, index) => {
3442
- const classes = getStepClasses(step.status);
3443
- const isLast = index === steps.length - 1;
3444
- const connectorHtml = !isLast && connector !== "none" ? `<div class="ml-5 w-0.5 h-8 ${connector === "dashed" ? "border-l-2 border-dashed border-gray-300" : "bg-gray-200"} ${step.status === "completed" ? "bg-success" : ""}"></div>` : "";
3445
- const stepContent = `
3446
- <div class="flex items-start gap-4">
3447
- <div class="w-10 h-10 rounded-full ${classes.circle} flex items-center justify-center flex-shrink-0">
3448
- ${getStepIcon(step, index)}
3449
- </div>
3450
- <div class="pt-2">
3451
- <div class="${classes.text}">${escapeHtml2(step.label)}</div>
3452
- ${step.description ? `<p class="text-sm text-text-secondary mt-0.5">${escapeHtml2(step.description)}</p>` : ""}
3453
- </div>
3454
- </div>
3455
- ${connectorHtml}
3456
- `;
3457
- return step.href && step.status === "completed" ? `<a href="${escapeHtml2(step.href)}" class="block hover:opacity-80">${stepContent}</a>` : `<div>${stepContent}</div>`;
3458
- }).join("\n");
3459
- return `<div class="step-progress ${className}">${stepsHtml2}</div>`;
3460
- }
3461
- const stepsHtml = steps.map((step, index) => {
3462
- const classes = getStepClasses(step.status);
3463
- const isLast = index === steps.length - 1;
3464
- const connectorHtml = !isLast && connector !== "none" ? `<div class="flex-1 h-0.5 mx-2 ${connector === "dashed" ? "border-t-2 border-dashed border-gray-300" : "bg-gray-200"} ${step.status === "completed" ? "bg-success" : ""}"></div>` : "";
3465
- const stepHtml = `
3466
- <div class="flex flex-col items-center">
3467
- <div class="w-10 h-10 rounded-full ${classes.circle} flex items-center justify-center">
3468
- ${getStepIcon(step, index)}
3469
- </div>
3470
- <div class="mt-2 text-center">
3471
- <div class="${classes.text} text-sm">${escapeHtml2(step.label)}</div>
3472
- ${step.description ? `<p class="text-xs text-text-secondary mt-0.5 max-w-[120px]">${escapeHtml2(step.description)}</p>` : ""}
3473
- </div>
3474
- </div>
3475
- `;
3476
- const clickableStep = step.href && step.status === "completed" ? `<a href="${escapeHtml2(step.href)}" class="hover:opacity-80">${stepHtml}</a>` : stepHtml;
3477
- return `${clickableStep}${connectorHtml}`;
3478
- }).join("\n");
3479
- return `<div class="step-progress flex items-start ${className}">${stepsHtml}</div>`;
3480
- }
3481
- function circularProgress(options) {
3482
- const { value, size = 80, strokeWidth = 8, variant = "primary", showLabel = true, label, className = "" } = options;
3483
- const clampedValue = Math.min(100, Math.max(0, value));
3484
- const radius = (size - strokeWidth) / 2;
3485
- const circumference = radius * 2 * Math.PI;
3486
- const offset = circumference - clampedValue / 100 * circumference;
3487
- const variantColors = {
3488
- primary: "text-primary",
3489
- success: "text-success",
3490
- warning: "text-warning",
3491
- danger: "text-danger"
3492
- };
3493
- const displayLabel = label || `${Math.round(clampedValue)}%`;
3494
- return `<div class="circular-progress inline-flex items-center justify-center ${className}" style="width: ${size}px; height: ${size}px;">
3495
- <svg class="transform -rotate-90" width="${size}" height="${size}">
3496
- <!-- Background circle -->
3497
- <circle
3498
- cx="${size / 2}"
3499
- cy="${size / 2}"
3500
- r="${radius}"
3501
- fill="none"
3502
- stroke="currentColor"
3503
- stroke-width="${strokeWidth}"
3504
- class="text-gray-200"
3505
- />
3506
- <!-- Progress circle -->
3507
- <circle
3508
- cx="${size / 2}"
3509
- cy="${size / 2}"
3510
- r="${radius}"
3511
- fill="none"
3512
- stroke="currentColor"
3513
- stroke-width="${strokeWidth}"
3514
- stroke-linecap="round"
3515
- class="${variantColors[variant]}"
3516
- style="stroke-dasharray: ${circumference}; stroke-dashoffset: ${offset}; transition: stroke-dashoffset 0.3s ease;"
3517
- />
3518
- </svg>
3519
- ${showLabel ? `<span class="absolute text-sm font-semibold text-text-primary">${escapeHtml2(displayLabel)}</span>` : ""}
3520
- </div>`;
3521
- }
3522
- function statusIndicator(options) {
3523
- const { status, label, size = "md", pulse = false, className = "" } = options;
3524
- const sizeClasses = {
3525
- sm: { dot: "w-2 h-2", text: "text-xs" },
3526
- md: { dot: "w-2.5 h-2.5", text: "text-sm" },
3527
- lg: { dot: "w-3 h-3", text: "text-base" }
3528
- };
3529
- const statusClasses = {
3530
- online: { color: "bg-success", label: "Online" },
3531
- offline: { color: "bg-gray-400", label: "Offline" },
3532
- busy: { color: "bg-danger", label: "Busy" },
3533
- away: { color: "bg-warning", label: "Away" },
3534
- loading: { color: "bg-blue-500", label: "Loading" },
3535
- error: { color: "bg-danger", label: "Error" },
3536
- success: { color: "bg-success", label: "Success" }
3537
- };
3538
- const statusInfo = statusClasses[status];
3539
- const sizeInfo = sizeClasses[size];
3540
- const displayLabel = label || statusInfo.label;
3541
- const pulseHtml = pulse || status === "loading" ? `<span class="absolute ${sizeInfo.dot} ${statusInfo.color} rounded-full animate-ping opacity-75"></span>` : "";
3542
- return `<div class="status-indicator inline-flex items-center gap-2 ${className}">
3543
- <span class="relative flex">
3544
- ${pulseHtml}
3545
- <span class="relative ${sizeInfo.dot} ${statusInfo.color} rounded-full"></span>
3546
- </span>
3547
- ${displayLabel ? `<span class="${sizeInfo.text} text-text-secondary">${escapeHtml2(displayLabel)}</span>` : ""}
3548
- </div>`;
3549
- }
3550
- function skeleton(options = {}) {
3551
- const { type = "text", width, height, lines = 3, animated = true, className = "" } = options;
3552
- const animateClass = animated ? "animate-pulse" : "";
3553
- const baseClass = `bg-gray-200 ${animateClass}`;
3554
- switch (type) {
3555
- case "circle":
3556
- return `<div class="${baseClass} rounded-full ${className}" style="width: ${width || "40px"}; height: ${height || "40px"}"></div>`;
3557
- case "rect":
3558
- return `<div class="${baseClass} rounded ${className}" style="width: ${width || "100%"}; height: ${height || "100px"}"></div>`;
3559
- case "card":
3560
- return `<div class="${animateClass} space-y-4 ${className}">
3561
- <div class="bg-gray-200 rounded h-40"></div>
3562
- <div class="space-y-2">
3563
- <div class="bg-gray-200 h-4 rounded w-3/4"></div>
3564
- <div class="bg-gray-200 h-4 rounded w-1/2"></div>
3565
- </div>
3566
- </div>`;
3567
- case "text":
3568
- default: {
3569
- const linesHtml = Array(lines).fill(0).map((_, i) => {
3570
- const lineWidth = i === lines - 1 ? "60%" : i === 0 ? "90%" : "80%";
3571
- return `<div class="bg-gray-200 h-4 rounded" style="width: ${lineWidth}"></div>`;
3572
- }).join("\n");
3573
- return `<div class="${animateClass} space-y-2 ${className}" style="width: ${width || "100%"}">
3574
- ${linesHtml}
3575
- </div>`;
3576
- }
3577
- }
3578
- }
3579
- function contentSkeleton(options = {}) {
3580
- const { animated = true, className = "" } = options;
3581
- const animateClass = animated ? "animate-pulse" : "";
3582
- return `<div class="${animateClass} flex gap-4 ${className}">
3583
- <div class="bg-gray-200 rounded-full w-12 h-12 flex-shrink-0"></div>
3584
- <div class="flex-1 space-y-2 py-1">
3585
- <div class="bg-gray-200 h-4 rounded w-3/4"></div>
3586
- <div class="bg-gray-200 h-4 rounded w-1/2"></div>
3587
- </div>
3588
- </div>`;
3589
- }
3590
-
3591
2671
  // libs/ui/src/bridge/core/adapter-registry.ts
3592
2672
  var AdapterRegistry = class {
3593
2673
  _adapters = /* @__PURE__ */ new Map();
@@ -3952,50 +3032,50 @@ var FrontMcpBridge = class {
3952
3032
  * Get current theme.
3953
3033
  */
3954
3034
  getTheme() {
3955
- this._ensureInitialized();
3956
- return this._adapter.getTheme();
3035
+ const adapter = this._ensureInitialized();
3036
+ return adapter.getTheme();
3957
3037
  }
3958
3038
  /**
3959
3039
  * Get current display mode.
3960
3040
  */
3961
3041
  getDisplayMode() {
3962
- this._ensureInitialized();
3963
- return this._adapter.getDisplayMode();
3042
+ const adapter = this._ensureInitialized();
3043
+ return adapter.getDisplayMode();
3964
3044
  }
3965
3045
  /**
3966
3046
  * Get tool input arguments.
3967
3047
  */
3968
3048
  getToolInput() {
3969
- this._ensureInitialized();
3970
- return this._adapter.getToolInput();
3049
+ const adapter = this._ensureInitialized();
3050
+ return adapter.getToolInput();
3971
3051
  }
3972
3052
  /**
3973
3053
  * Get tool output/result.
3974
3054
  */
3975
3055
  getToolOutput() {
3976
- this._ensureInitialized();
3977
- return this._adapter.getToolOutput();
3056
+ const adapter = this._ensureInitialized();
3057
+ return adapter.getToolOutput();
3978
3058
  }
3979
3059
  /**
3980
3060
  * Get structured content (parsed output).
3981
3061
  */
3982
3062
  getStructuredContent() {
3983
- this._ensureInitialized();
3984
- return this._adapter.getStructuredContent();
3063
+ const adapter = this._ensureInitialized();
3064
+ return adapter.getStructuredContent();
3985
3065
  }
3986
3066
  /**
3987
3067
  * Get persisted widget state.
3988
3068
  */
3989
3069
  getWidgetState() {
3990
- this._ensureInitialized();
3991
- return this._adapter.getWidgetState();
3070
+ const adapter = this._ensureInitialized();
3071
+ return adapter.getWidgetState();
3992
3072
  }
3993
3073
  /**
3994
3074
  * Get full host context.
3995
3075
  */
3996
3076
  getHostContext() {
3997
- this._ensureInitialized();
3998
- return this._adapter.getHostContext();
3077
+ const adapter = this._ensureInitialized();
3078
+ return adapter.getHostContext();
3999
3079
  }
4000
3080
  // ============================================
4001
3081
  // Actions (delegate to adapter)
@@ -4006,2026 +3086,139 @@ var FrontMcpBridge = class {
4006
3086
  * @param args - Tool arguments
4007
3087
  */
4008
3088
  async callTool(name, args) {
4009
- this._ensureInitialized();
3089
+ const adapter = this._ensureInitialized();
4010
3090
  if (!this.hasCapability("canCallTools")) {
4011
3091
  throw new Error("Tool calls are not supported by the current adapter");
4012
3092
  }
4013
- return this._adapter.callTool(name, args);
3093
+ return adapter.callTool(name, args);
4014
3094
  }
4015
3095
  /**
4016
3096
  * Send a follow-up message to the conversation.
4017
3097
  * @param content - Message content
4018
3098
  */
4019
3099
  async sendMessage(content) {
4020
- this._ensureInitialized();
3100
+ const adapter = this._ensureInitialized();
4021
3101
  if (!this.hasCapability("canSendMessages")) {
4022
3102
  throw new Error("Sending messages is not supported by the current adapter");
4023
3103
  }
4024
- return this._adapter.sendMessage(content);
3104
+ return adapter.sendMessage(content);
4025
3105
  }
4026
3106
  /**
4027
3107
  * Open an external link.
4028
3108
  * @param url - URL to open
4029
3109
  */
4030
3110
  async openLink(url) {
4031
- this._ensureInitialized();
4032
- return this._adapter.openLink(url);
3111
+ const adapter = this._ensureInitialized();
3112
+ return adapter.openLink(url);
4033
3113
  }
4034
3114
  /**
4035
3115
  * Request a display mode change.
4036
3116
  * @param mode - Desired display mode
4037
3117
  */
4038
3118
  async requestDisplayMode(mode) {
4039
- this._ensureInitialized();
4040
- return this._adapter.requestDisplayMode(mode);
3119
+ const adapter = this._ensureInitialized();
3120
+ return adapter.requestDisplayMode(mode);
4041
3121
  }
4042
3122
  /**
4043
3123
  * Request widget close.
4044
3124
  */
4045
3125
  async requestClose() {
4046
- this._ensureInitialized();
4047
- return this._adapter.requestClose();
3126
+ const adapter = this._ensureInitialized();
3127
+ return adapter.requestClose();
4048
3128
  }
4049
3129
  /**
4050
3130
  * Set widget state (persisted across sessions).
4051
3131
  * @param state - State object to persist
4052
3132
  */
4053
3133
  setWidgetState(state) {
4054
- this._ensureInitialized();
4055
- this._adapter.setWidgetState(state);
4056
- }
4057
- // ============================================
4058
- // Events (delegate to adapter)
4059
- // ============================================
4060
- /**
4061
- * Subscribe to host context changes.
4062
- * @param callback - Called when context changes
4063
- * @returns Unsubscribe function
4064
- */
4065
- onContextChange(callback) {
4066
- this._ensureInitialized();
4067
- return this._adapter.onContextChange(callback);
4068
- }
4069
- /**
4070
- * Subscribe to tool result updates.
4071
- * @param callback - Called when tool result is received
4072
- * @returns Unsubscribe function
4073
- */
4074
- onToolResult(callback) {
4075
- this._ensureInitialized();
4076
- return this._adapter.onToolResult(callback);
3134
+ const adapter = this._ensureInitialized();
3135
+ adapter.setWidgetState(state);
4077
3136
  }
4078
3137
  // ============================================
4079
- // Private Helpers
4080
- // ============================================
4081
- /**
4082
- * Ensure the bridge is initialized before operations.
4083
- */
4084
- _ensureInitialized() {
4085
- if (!this._initialized || !this._adapter) {
4086
- throw new Error("FrontMcpBridge is not initialized. Call initialize() first.");
4087
- }
4088
- }
4089
- /**
4090
- * Wrap a promise with a timeout.
4091
- */
4092
- _withTimeout(promise, ms) {
4093
- return new Promise((resolve, reject) => {
4094
- const timer = setTimeout(() => {
4095
- reject(new Error(`Operation timed out after ${ms}ms`));
4096
- }, ms);
4097
- promise.then((result) => {
4098
- clearTimeout(timer);
4099
- resolve(result);
4100
- }).catch((error) => {
4101
- clearTimeout(timer);
4102
- reject(error);
4103
- });
4104
- });
4105
- }
4106
- /**
4107
- * Emit a bridge event via CustomEvent.
4108
- */
4109
- _emitEvent(type, payload) {
4110
- if (typeof window !== "undefined" && typeof CustomEvent !== "undefined") {
4111
- try {
4112
- const event = new CustomEvent(type, { detail: payload });
4113
- window.dispatchEvent(event);
4114
- } catch {
4115
- }
4116
- }
4117
- }
4118
- /**
4119
- * Log debug message if debugging is enabled.
4120
- */
4121
- _log(message) {
4122
- if (this._config.debug) {
4123
- console.log(`[FrontMcpBridge] ${message}`);
4124
- }
4125
- }
4126
- };
4127
- async function createBridge(config, registry) {
4128
- const bridge = new FrontMcpBridge(config, registry);
4129
- await bridge.initialize();
4130
- return bridge;
4131
- }
4132
-
4133
- // libs/ui/src/bridge/adapters/base-adapter.ts
4134
- var DEFAULT_CAPABILITIES = {
4135
- canCallTools: false,
4136
- canSendMessages: false,
4137
- canOpenLinks: false,
4138
- canPersistState: true,
4139
- // localStorage fallback
4140
- hasNetworkAccess: true,
4141
- supportsDisplayModes: false,
4142
- supportsTheme: true
4143
- };
4144
- var DEFAULT_SAFE_AREA = {
4145
- top: 0,
4146
- bottom: 0,
4147
- left: 0,
4148
- right: 0
4149
- };
4150
- var BaseAdapter = class {
4151
- _capabilities = { ...DEFAULT_CAPABILITIES };
4152
- _hostContext;
4153
- _widgetState = {};
4154
- _toolInput = {};
4155
- _toolOutput = void 0;
4156
- _structuredContent = void 0;
4157
- _initialized = false;
4158
- _contextListeners = /* @__PURE__ */ new Set();
4159
- _toolResultListeners = /* @__PURE__ */ new Set();
4160
- constructor() {
4161
- this._hostContext = this._createDefaultHostContext();
4162
- }
4163
- get capabilities() {
4164
- return this._capabilities;
4165
- }
4166
- async initialize() {
4167
- if (this._initialized) return;
4168
- this._loadWidgetState();
4169
- this._readInjectedData();
4170
- this._initialized = true;
4171
- }
4172
- dispose() {
4173
- this._contextListeners.clear();
4174
- this._toolResultListeners.clear();
4175
- this._initialized = false;
4176
- }
4177
- // ============================================
4178
- // Data Access
4179
- // ============================================
4180
- getTheme() {
4181
- return this._hostContext.theme;
4182
- }
4183
- getDisplayMode() {
4184
- return this._hostContext.displayMode;
4185
- }
4186
- getUserAgent() {
4187
- return this._hostContext.userAgent;
4188
- }
4189
- getLocale() {
4190
- return this._hostContext.locale;
4191
- }
4192
- getToolInput() {
4193
- return this._toolInput;
4194
- }
4195
- getToolOutput() {
4196
- return this._toolOutput;
4197
- }
4198
- getStructuredContent() {
4199
- return this._structuredContent;
4200
- }
4201
- getWidgetState() {
4202
- return this._widgetState;
4203
- }
4204
- getSafeArea() {
4205
- return this._hostContext.safeArea;
4206
- }
4207
- getViewport() {
4208
- return this._hostContext.viewport;
4209
- }
4210
- getHostContext() {
4211
- return { ...this._hostContext };
4212
- }
4213
- // ============================================
4214
- // Actions (override in subclasses for real functionality)
4215
- // ============================================
4216
- async callTool(_name, _args) {
4217
- if (!this._capabilities.canCallTools) {
4218
- throw new Error(`Tool calls are not supported by ${this.name} adapter`);
4219
- }
4220
- throw new Error("callTool not implemented");
4221
- }
4222
- async sendMessage(_content) {
4223
- if (!this._capabilities.canSendMessages) {
4224
- throw new Error(`Sending messages is not supported by ${this.name} adapter`);
4225
- }
4226
- throw new Error("sendMessage not implemented");
4227
- }
4228
- async openLink(url) {
4229
- if (!this._capabilities.canOpenLinks) {
4230
- if (typeof window !== "undefined") {
4231
- window.open(url, "_blank", "noopener,noreferrer");
4232
- return;
4233
- }
4234
- throw new Error(`Opening links is not supported by ${this.name} adapter`);
4235
- }
4236
- throw new Error("openLink not implemented");
4237
- }
4238
- async requestDisplayMode(_mode) {
4239
- if (!this._capabilities.supportsDisplayModes) {
4240
- return;
4241
- }
4242
- throw new Error("requestDisplayMode not implemented");
4243
- }
4244
- async requestClose() {
4245
- }
4246
- setWidgetState(state) {
4247
- this._widgetState = { ...this._widgetState, ...state };
4248
- this._persistWidgetState();
4249
- }
4250
- // ============================================
4251
- // Events
4252
- // ============================================
4253
- onContextChange(callback) {
4254
- this._contextListeners.add(callback);
4255
- return () => {
4256
- this._contextListeners.delete(callback);
4257
- };
4258
- }
4259
- onToolResult(callback) {
4260
- this._toolResultListeners.add(callback);
4261
- return () => {
4262
- this._toolResultListeners.delete(callback);
4263
- };
4264
- }
4265
- // ============================================
4266
- // Protected Helpers
4267
- // ============================================
4268
- /**
4269
- * Create default host context from environment detection.
4270
- */
4271
- _createDefaultHostContext() {
4272
- return {
4273
- theme: this._detectTheme(),
4274
- displayMode: "inline",
4275
- locale: this._detectLocale(),
4276
- userAgent: this._detectUserAgent(),
4277
- safeArea: DEFAULT_SAFE_AREA,
4278
- viewport: this._detectViewport()
4279
- };
4280
- }
4281
- /**
4282
- * Detect theme from CSS media query.
4283
- */
4284
- _detectTheme() {
4285
- if (typeof window !== "undefined" && window.matchMedia) {
4286
- return window.matchMedia("(prefers-color-scheme: dark)").matches ? "dark" : "light";
4287
- }
4288
- return "light";
4289
- }
4290
- /**
4291
- * Detect locale from navigator.
4292
- */
4293
- _detectLocale() {
4294
- if (typeof navigator !== "undefined") {
4295
- return navigator.language || "en-US";
4296
- }
4297
- return "en-US";
4298
- }
4299
- /**
4300
- * Detect user agent capabilities.
4301
- */
4302
- _detectUserAgent() {
4303
- if (typeof navigator === "undefined") {
4304
- return { type: "web", hover: true, touch: false };
4305
- }
4306
- const ua = navigator.userAgent || "";
4307
- const isMobile = /iPhone|iPad|iPod|Android/i.test(ua);
4308
- const hasTouch = "ontouchstart" in window || navigator.maxTouchPoints > 0;
4309
- const hasHover = typeof window !== "undefined" && window.matchMedia && window.matchMedia("(hover: hover)").matches;
4310
- return {
4311
- type: isMobile ? "mobile" : "web",
4312
- hover: hasHover !== false,
4313
- touch: hasTouch
4314
- };
4315
- }
4316
- /**
4317
- * Detect viewport dimensions.
4318
- */
4319
- _detectViewport() {
4320
- if (typeof window !== "undefined") {
4321
- return {
4322
- width: window.innerWidth,
4323
- height: window.innerHeight
4324
- };
4325
- }
4326
- return void 0;
4327
- }
4328
- /**
4329
- * Read injected tool data from window globals.
4330
- */
4331
- _readInjectedData() {
4332
- if (typeof window === "undefined") return;
4333
- const win = window;
4334
- if (win.__mcpToolInput) {
4335
- this._toolInput = win.__mcpToolInput;
4336
- }
4337
- if (win.__mcpToolOutput) {
4338
- this._toolOutput = win.__mcpToolOutput;
4339
- }
4340
- if (win.__mcpStructuredContent) {
4341
- this._structuredContent = win.__mcpStructuredContent;
4342
- }
4343
- if (win.__mcpHostContext) {
4344
- this._hostContext = { ...this._hostContext, ...win.__mcpHostContext };
4345
- }
4346
- }
4347
- /**
4348
- * Load widget state from localStorage.
4349
- */
4350
- _loadWidgetState() {
4351
- if (typeof localStorage === "undefined") return;
4352
- try {
4353
- const key = this._getStateKey();
4354
- const stored = localStorage.getItem(key);
4355
- if (stored) {
4356
- this._widgetState = JSON.parse(stored);
4357
- }
4358
- } catch {
4359
- }
4360
- }
4361
- /**
4362
- * Persist widget state to localStorage.
4363
- */
4364
- _persistWidgetState() {
4365
- if (typeof localStorage === "undefined") return;
4366
- try {
4367
- const key = this._getStateKey();
4368
- localStorage.setItem(key, JSON.stringify(this._widgetState));
4369
- } catch {
4370
- }
4371
- }
4372
- /**
4373
- * Get localStorage key for widget state.
4374
- */
4375
- _getStateKey() {
4376
- if (typeof window !== "undefined") {
4377
- const toolName = window.__mcpToolName || "unknown";
4378
- return `frontmcp:widget:${toolName}`;
4379
- }
4380
- return "frontmcp:widget:unknown";
4381
- }
4382
- /**
4383
- * Notify context change listeners.
4384
- */
4385
- _notifyContextChange(changes) {
4386
- this._hostContext = { ...this._hostContext, ...changes };
4387
- this._contextListeners.forEach((cb) => {
4388
- try {
4389
- cb(changes);
4390
- } catch (e) {
4391
- console.error("[FrontMcpBridge] Context change listener error:", e);
4392
- }
4393
- });
4394
- }
4395
- /**
4396
- * Notify tool result listeners.
4397
- */
4398
- _notifyToolResult(result) {
4399
- this._toolOutput = result;
4400
- this._toolResultListeners.forEach((cb) => {
4401
- try {
4402
- cb(result);
4403
- } catch (e) {
4404
- console.error("[FrontMcpBridge] Tool result listener error:", e);
4405
- }
4406
- });
4407
- }
4408
- };
4409
-
4410
- // libs/ui/src/bridge/adapters/openai.adapter.ts
4411
- var OpenAIAdapter = class extends BaseAdapter {
4412
- id = "openai";
4413
- name = "OpenAI ChatGPT";
4414
- priority = 100;
4415
- // Highest priority
4416
- _openai;
4417
- _unsubscribeContext;
4418
- _unsubscribeToolResult;
4419
- constructor() {
4420
- super();
4421
- this._capabilities = {
4422
- ...DEFAULT_CAPABILITIES,
4423
- canCallTools: true,
4424
- canSendMessages: true,
4425
- canOpenLinks: true,
4426
- canPersistState: true,
4427
- hasNetworkAccess: true,
4428
- supportsDisplayModes: true,
4429
- supportsTheme: true
4430
- };
4431
- }
4432
- /**
4433
- * Check if OpenAI Apps SDK is available.
4434
- */
4435
- canHandle() {
4436
- if (typeof window === "undefined") return false;
4437
- const win = window;
4438
- return Boolean(win.openai?.canvas);
4439
- }
4440
- /**
4441
- * Initialize the OpenAI adapter.
4442
- */
4443
- async initialize() {
4444
- if (this._initialized) return;
4445
- this._openai = window.openai;
4446
- await super.initialize();
4447
- this._syncContextFromSDK();
4448
- if (this._openai?.canvas?.onContextChange) {
4449
- this._unsubscribeContext = this._openai.canvas.onContextChange((changes) => {
4450
- this._notifyContextChange(changes);
4451
- });
4452
- }
4453
- if (this._openai?.canvas?.onToolResult) {
4454
- this._unsubscribeToolResult = this._openai.canvas.onToolResult((result) => {
4455
- this._notifyToolResult(result);
4456
- });
4457
- }
4458
- }
4459
- /**
4460
- * Dispose adapter resources.
4461
- */
4462
- dispose() {
4463
- if (this._unsubscribeContext) {
4464
- this._unsubscribeContext();
4465
- this._unsubscribeContext = void 0;
4466
- }
4467
- if (this._unsubscribeToolResult) {
4468
- this._unsubscribeToolResult();
4469
- this._unsubscribeToolResult = void 0;
4470
- }
4471
- this._openai = void 0;
4472
- super.dispose();
4473
- }
4474
- // ============================================
4475
- // Data Access (override with SDK calls)
4476
- // ============================================
4477
- getTheme() {
4478
- if (this._openai?.canvas?.getTheme) {
4479
- const theme = this._openai.canvas.getTheme();
4480
- return theme === "dark" ? "dark" : "light";
4481
- }
4482
- return super.getTheme();
4483
- }
4484
- getDisplayMode() {
4485
- if (this._openai?.canvas?.getDisplayMode) {
4486
- const mode = this._openai.canvas.getDisplayMode();
4487
- if (mode === "fullscreen" || mode === "pip" || mode === "carousel") {
4488
- return mode;
4489
- }
4490
- return "inline";
4491
- }
4492
- return super.getDisplayMode();
4493
- }
4494
- // ============================================
4495
- // Actions (proxy to SDK)
4496
- // ============================================
4497
- async callTool(name, args) {
4498
- if (!this._openai?.canvas?.callServerTool) {
4499
- throw new Error("callServerTool not available in OpenAI SDK");
4500
- }
4501
- return this._openai.canvas.callServerTool(name, args);
4502
- }
4503
- async sendMessage(content) {
4504
- if (!this._openai?.canvas?.sendMessage) {
4505
- throw new Error("sendMessage not available in OpenAI SDK");
4506
- }
4507
- await this._openai.canvas.sendMessage(content);
4508
- }
4509
- async openLink(url) {
4510
- if (!this._openai?.canvas?.openLink) {
4511
- return super.openLink(url);
4512
- }
4513
- await this._openai.canvas.openLink(url);
4514
- }
4515
- async requestDisplayMode(mode) {
4516
- if (!this._openai?.canvas?.setDisplayMode) {
4517
- return super.requestDisplayMode(mode);
4518
- }
4519
- await this._openai.canvas.setDisplayMode(mode);
4520
- this._hostContext = { ...this._hostContext, displayMode: mode };
4521
- }
4522
- async requestClose() {
4523
- if (this._openai?.canvas?.close) {
4524
- await this._openai.canvas.close();
4525
- }
4526
- }
4527
- // ============================================
4528
- // Private Helpers
4529
- // ============================================
4530
- /**
4531
- * Sync context from OpenAI SDK.
4532
- */
4533
- _syncContextFromSDK() {
4534
- if (!this._openai?.canvas) return;
4535
- if (this._openai.canvas.getTheme) {
4536
- const theme = this._openai.canvas.getTheme();
4537
- this._hostContext.theme = theme === "dark" ? "dark" : "light";
4538
- }
4539
- if (this._openai.canvas.getDisplayMode) {
4540
- const mode = this._openai.canvas.getDisplayMode();
4541
- if (mode === "fullscreen" || mode === "pip" || mode === "carousel" || mode === "inline") {
4542
- this._hostContext.displayMode = mode;
4543
- }
4544
- }
4545
- if (this._openai.canvas.getContext) {
4546
- const ctx = this._openai.canvas.getContext();
4547
- if (ctx) {
4548
- this._hostContext = { ...this._hostContext, ...ctx };
4549
- }
4550
- }
4551
- }
4552
- };
4553
- function createOpenAIAdapter() {
4554
- return new OpenAIAdapter();
4555
- }
4556
-
4557
- // libs/ui/src/bridge/adapters/ext-apps.adapter.ts
4558
- var ExtAppsAdapter = class extends BaseAdapter {
4559
- id = "ext-apps";
4560
- name = "ext-apps (SEP-1865)";
4561
- priority = 80;
4562
- // High priority, but below OpenAI native
4563
- _config;
4564
- _messageListener;
4565
- _pendingRequests = /* @__PURE__ */ new Map();
4566
- _requestId = 0;
4567
- _trustedOrigin;
4568
- _hostCapabilities = {};
4569
- constructor(config) {
4570
- super();
4571
- this._config = config || {};
4572
- this._capabilities = {
4573
- ...DEFAULT_CAPABILITIES,
4574
- canPersistState: true,
4575
- hasNetworkAccess: true,
4576
- // ext-apps usually allows network
4577
- supportsTheme: true
4578
- };
4579
- }
4580
- /**
4581
- * Check if we're in an iframe (potential ext-apps context).
4582
- */
4583
- canHandle() {
4584
- if (typeof window === "undefined") return false;
4585
- const inIframe = window.parent !== window;
4586
- if (!inIframe) return false;
4587
- const win = window;
4588
- if (win.openai?.canvas) return false;
4589
- if (win.__mcpPlatform === "ext-apps") return true;
4590
- return true;
4591
- }
4592
- /**
4593
- * Initialize the ext-apps adapter with protocol handshake.
4594
- */
4595
- async initialize() {
4596
- if (this._initialized) return;
4597
- this._setupMessageListener();
4598
- await super.initialize();
4599
- await this._performHandshake();
4600
- this._initialized = true;
4601
- }
4602
- /**
4603
- * Dispose adapter resources.
4604
- */
4605
- dispose() {
4606
- if (this._messageListener && typeof window !== "undefined") {
4607
- window.removeEventListener("message", this._messageListener);
4608
- this._messageListener = void 0;
4609
- }
4610
- for (const [id, pending] of this._pendingRequests) {
4611
- clearTimeout(pending.timeout);
4612
- pending.reject(new Error("Adapter disposed"));
4613
- }
4614
- this._pendingRequests.clear();
4615
- super.dispose();
4616
- }
4617
- // ============================================
4618
- // Actions (via JSON-RPC)
4619
- // ============================================
4620
- async callTool(name, args) {
4621
- if (!this._hostCapabilities.serverToolProxy) {
4622
- throw new Error("Server tool proxy not supported by host");
4623
- }
4624
- return this._sendRequest("ui/callServerTool", {
4625
- name,
4626
- arguments: args
4627
- });
4628
- }
4629
- async sendMessage(content) {
4630
- await this._sendRequest("ui/message", { content });
4631
- }
4632
- async openLink(url) {
4633
- if (!this._hostCapabilities.openLink) {
4634
- return super.openLink(url);
4635
- }
4636
- await this._sendRequest("ui/openLink", { url });
4637
- }
4638
- async requestDisplayMode(mode) {
4639
- await this._sendRequest("ui/setDisplayMode", { mode });
4640
- this._hostContext = { ...this._hostContext, displayMode: mode };
4641
- }
4642
- async requestClose() {
4643
- await this._sendRequest("ui/close", {});
4644
- }
4645
- // ============================================
4646
- // Private: Message Handling
4647
- // ============================================
4648
- /**
4649
- * Setup postMessage listener for incoming messages.
4650
- */
4651
- _setupMessageListener() {
4652
- if (typeof window === "undefined") return;
4653
- this._messageListener = (event) => {
4654
- this._handleMessage(event);
4655
- };
4656
- window.addEventListener("message", this._messageListener);
4657
- }
4658
- /**
4659
- * Handle incoming postMessage events.
4660
- */
4661
- _handleMessage(event) {
4662
- if (!this._isOriginTrusted(event.origin)) {
4663
- return;
4664
- }
4665
- const data = event.data;
4666
- if (!data || typeof data !== "object") return;
4667
- if (data.jsonrpc !== "2.0") return;
4668
- if ("id" in data && (data.result !== void 0 || data.error !== void 0)) {
4669
- this._handleResponse(data);
4670
- return;
4671
- }
4672
- if ("method" in data && !("id" in data)) {
4673
- this._handleNotification(data);
4674
- return;
4675
- }
4676
- }
4677
- /**
4678
- * Handle JSON-RPC response.
4679
- */
4680
- _handleResponse(response) {
4681
- const pending = this._pendingRequests.get(response.id);
4682
- if (!pending) return;
4683
- clearTimeout(pending.timeout);
4684
- this._pendingRequests.delete(response.id);
4685
- if (response.error) {
4686
- pending.reject(new Error(`${response.error.message} (code: ${response.error.code})`));
4687
- } else {
4688
- pending.resolve(response.result);
4689
- }
4690
- }
4691
- /**
4692
- * Handle JSON-RPC notification from host.
4693
- */
4694
- _handleNotification(notification) {
4695
- switch (notification.method) {
4696
- case "ui/notifications/tool-input":
4697
- this._handleToolInput(notification.params);
4698
- break;
4699
- case "ui/notifications/tool-input-partial":
4700
- this._handleToolInputPartial(notification.params);
4701
- break;
4702
- case "ui/notifications/tool-result":
4703
- this._handleToolResult(notification.params);
4704
- break;
4705
- case "ui/notifications/host-context-changed":
4706
- this._handleHostContextChange(notification.params);
4707
- break;
4708
- case "ui/notifications/initialized":
4709
- break;
4710
- case "ui/notifications/cancelled":
4711
- this._handleCancelled(notification.params);
4712
- break;
4713
- }
4714
- }
4715
- /**
4716
- * Handle tool input notification.
4717
- */
4718
- _handleToolInput(params) {
4719
- this._toolInput = params.arguments || {};
4720
- this._emitBridgeEvent("tool:input", { arguments: this._toolInput });
4721
- }
4722
- /**
4723
- * Handle partial tool input (streaming).
4724
- */
4725
- _handleToolInputPartial(params) {
4726
- this._toolInput = { ...this._toolInput, ...params.arguments };
4727
- this._emitBridgeEvent("tool:input-partial", { arguments: this._toolInput });
4728
- }
4729
- /**
4730
- * Handle tool result notification.
4731
- */
4732
- _handleToolResult(params) {
4733
- this._toolOutput = params.content;
4734
- this._structuredContent = params.structuredContent;
4735
- this._notifyToolResult(params.content);
4736
- this._emitBridgeEvent("tool:result", {
4737
- content: params.content,
4738
- structuredContent: params.structuredContent
4739
- });
4740
- }
4741
- /**
4742
- * Handle host context change notification.
4743
- */
4744
- _handleHostContextChange(params) {
4745
- const changes = {};
4746
- if (params.theme !== void 0) {
4747
- changes.theme = params.theme;
4748
- }
4749
- if (params.displayMode !== void 0) {
4750
- changes.displayMode = params.displayMode;
4751
- }
4752
- if (params.viewport !== void 0) {
4753
- changes.viewport = params.viewport;
4754
- }
4755
- if (params.locale !== void 0) {
4756
- changes.locale = params.locale;
4757
- }
4758
- if (params.timezone !== void 0) {
4759
- changes.timezone = params.timezone;
4760
- }
4761
- this._notifyContextChange(changes);
4762
- }
4763
- /**
4764
- * Handle cancellation notification.
4765
- */
4766
- _handleCancelled(params) {
4767
- const reason = params?.reason;
4768
- this._emitBridgeEvent("tool:cancelled", { reason });
4769
- }
4770
- // ============================================
4771
- // Private: JSON-RPC Transport
4772
- // ============================================
4773
- /**
4774
- * Send a JSON-RPC request to the host.
4775
- */
4776
- _sendRequest(method, params) {
4777
- return new Promise((resolve, reject) => {
4778
- const id = ++this._requestId;
4779
- const timeout = this._config.options?.initTimeout || 1e4;
4780
- const request = {
4781
- jsonrpc: "2.0",
4782
- id,
4783
- method,
4784
- params
4785
- };
4786
- const timeoutHandle = setTimeout(() => {
4787
- this._pendingRequests.delete(id);
4788
- reject(new Error(`Request ${method} timed out after ${timeout}ms`));
4789
- }, timeout);
4790
- this._pendingRequests.set(id, {
4791
- resolve,
4792
- reject,
4793
- timeout: timeoutHandle
4794
- });
4795
- this._postMessage(request);
4796
- });
4797
- }
4798
- /**
4799
- * Send a JSON-RPC notification (no response expected).
4800
- */
4801
- _sendNotification(method, params) {
4802
- const notification = {
4803
- jsonrpc: "2.0",
4804
- method,
4805
- params
4806
- };
4807
- this._postMessage(notification);
4808
- }
4809
- /**
4810
- * Post a message to the parent window.
4811
- */
4812
- _postMessage(message) {
4813
- if (typeof window === "undefined") return;
4814
- const targetOrigin = this._trustedOrigin || "*";
4815
- window.parent.postMessage(message, targetOrigin);
4816
- }
4817
- // ============================================
4818
- // Private: Handshake
4819
- // ============================================
4820
- /**
4821
- * Perform the ui/initialize handshake with the host.
4822
- */
4823
- async _performHandshake() {
4824
- const params = {
4825
- appInfo: {
4826
- name: this._config.options?.appName || "FrontMCP Widget",
4827
- version: this._config.options?.appVersion || "1.0.0"
4828
- },
4829
- appCapabilities: {
4830
- tools: {
4831
- listChanged: false
4832
- }
4833
- },
4834
- protocolVersion: this._config.options?.protocolVersion || "2024-11-05"
4835
- };
4836
- try {
4837
- const result = await this._sendRequest("ui/initialize", params);
4838
- this._hostCapabilities = result.hostCapabilities || {};
4839
- this._capabilities = {
4840
- ...this._capabilities,
4841
- canCallTools: Boolean(this._hostCapabilities.serverToolProxy),
4842
- canSendMessages: true,
4843
- canOpenLinks: Boolean(this._hostCapabilities.openLink),
4844
- supportsDisplayModes: true
4845
- };
4846
- if (result.hostContext) {
4847
- this._hostContext = {
4848
- ...this._hostContext,
4849
- ...result.hostContext
4850
- };
4851
- }
4852
- if (!this._config.options?.trustedOrigins?.length) {
4853
- }
4854
- } catch (error) {
4855
- throw new Error(`ext-apps handshake failed: ${error}`);
4856
- }
4857
- }
4858
- // ============================================
4859
- // Private: Origin Security
4860
- // ============================================
4861
- /**
4862
- * Check if an origin is trusted.
4863
- * Uses trust-on-first-use if no explicit origins configured.
4864
- */
4865
- _isOriginTrusted(origin) {
4866
- const trustedOrigins = this._config.options?.trustedOrigins;
4867
- if (trustedOrigins && trustedOrigins.length > 0) {
4868
- return trustedOrigins.includes(origin);
4869
- }
4870
- if (!this._trustedOrigin) {
4871
- this._trustedOrigin = origin;
4872
- return true;
4873
- }
4874
- return this._trustedOrigin === origin;
4875
- }
4876
- // ============================================
4877
- // Private: Events
4878
- // ============================================
4879
- /**
4880
- * Emit a bridge event via CustomEvent.
4881
- */
4882
- _emitBridgeEvent(type, detail) {
4883
- if (typeof window !== "undefined" && typeof CustomEvent !== "undefined") {
4884
- try {
4885
- const event = new CustomEvent(type, { detail });
4886
- window.dispatchEvent(event);
4887
- } catch {
4888
- }
4889
- }
4890
- }
4891
- };
4892
- function createExtAppsAdapter(config) {
4893
- return new ExtAppsAdapter(config);
4894
- }
4895
-
4896
- // libs/ui/src/bridge/adapters/claude.adapter.ts
4897
- var ClaudeAdapter = class extends BaseAdapter {
4898
- id = "claude";
4899
- name = "Claude (Anthropic)";
4900
- priority = 60;
4901
- constructor() {
4902
- super();
4903
- this._capabilities = {
4904
- ...DEFAULT_CAPABILITIES,
4905
- canCallTools: false,
4906
- // Claude artifacts can't call tools
4907
- canSendMessages: false,
4908
- // Can't send messages back to conversation
4909
- canOpenLinks: true,
4910
- // Can open links via window.open
4911
- canPersistState: true,
4912
- // localStorage works
4913
- hasNetworkAccess: false,
4914
- // Network is blocked
4915
- supportsDisplayModes: false,
4916
- // No display mode control
4917
- supportsTheme: true
4918
- // Can detect system theme
4919
- };
4920
- }
4921
- /**
4922
- * Check if we're running in a Claude artifact/widget context.
4923
- */
4924
- canHandle() {
4925
- if (typeof window === "undefined") return false;
4926
- const win = window;
4927
- if (win.__mcpPlatform === "claude") return true;
4928
- if (win.claude) return true;
4929
- if (win.__claudeArtifact) return true;
4930
- if (typeof location !== "undefined") {
4931
- const href = location.href;
4932
- if (href.includes("claude.ai") || href.includes("anthropic.com")) {
4933
- return true;
4934
- }
4935
- }
4936
- return false;
4937
- }
4938
- /**
4939
- * Initialize the Claude adapter.
4940
- */
4941
- async initialize() {
4942
- if (this._initialized) return;
4943
- await super.initialize();
4944
- this._setupThemeListener();
4945
- }
4946
- /**
4947
- * Open a link in a new tab.
4948
- * This is one of the few actions available in Claude artifacts.
4949
- */
4950
- async openLink(url) {
4951
- if (typeof window !== "undefined") {
4952
- window.open(url, "_blank", "noopener,noreferrer");
4953
- }
4954
- }
4955
- /**
4956
- * Request display mode change (no-op for Claude).
4957
- */
4958
- async requestDisplayMode(_mode) {
4959
- }
4960
- /**
4961
- * Request close (no-op for Claude).
4962
- */
4963
- async requestClose() {
4964
- }
4965
- // ============================================
4966
- // Private Helpers
4967
- // ============================================
4968
- /**
4969
- * Setup listener for system theme changes.
4970
- */
4971
- _setupThemeListener() {
4972
- if (typeof window === "undefined" || !window.matchMedia) return;
4973
- const mediaQuery = window.matchMedia("(prefers-color-scheme: dark)");
4974
- const handleChange = (e) => {
4975
- const newTheme = e.matches ? "dark" : "light";
4976
- if (newTheme !== this._hostContext.theme) {
4977
- this._notifyContextChange({ theme: newTheme });
4978
- }
4979
- };
4980
- if (mediaQuery.addEventListener) {
4981
- mediaQuery.addEventListener("change", handleChange);
4982
- } else if (mediaQuery.addListener) {
4983
- mediaQuery.addListener(handleChange);
4984
- }
4985
- }
4986
- };
4987
- function createClaudeAdapter() {
4988
- return new ClaudeAdapter();
4989
- }
4990
-
4991
- // libs/ui/src/bridge/adapters/gemini.adapter.ts
4992
- var GeminiAdapter = class extends BaseAdapter {
4993
- id = "gemini";
4994
- name = "Google Gemini";
4995
- priority = 40;
4996
- _gemini;
4997
- constructor() {
4998
- super();
4999
- this._capabilities = {
5000
- ...DEFAULT_CAPABILITIES,
5001
- canCallTools: false,
5002
- // May be enabled if SDK supports it
5003
- canSendMessages: false,
5004
- // May be enabled if SDK supports it
5005
- canOpenLinks: true,
5006
- canPersistState: true,
5007
- hasNetworkAccess: true,
5008
- supportsDisplayModes: false,
5009
- supportsTheme: true
5010
- };
5011
- }
5012
- /**
5013
- * Check if we're running in a Gemini context.
5014
- */
5015
- canHandle() {
5016
- if (typeof window === "undefined") return false;
5017
- const win = window;
5018
- if (win.__mcpPlatform === "gemini") return true;
5019
- if (win.gemini) return true;
5020
- if (typeof location !== "undefined") {
5021
- const href = location.href;
5022
- if (href.includes("gemini.google.com") || href.includes("bard.google.com")) {
5023
- return true;
5024
- }
5025
- }
5026
- return false;
5027
- }
5028
- /**
5029
- * Initialize the Gemini adapter.
5030
- */
5031
- async initialize() {
5032
- if (this._initialized) return;
5033
- const win = window;
5034
- this._gemini = win.gemini;
5035
- if (this._gemini?.ui) {
5036
- if (this._gemini.ui.sendMessage) {
5037
- this._capabilities = { ...this._capabilities, canSendMessages: true };
5038
- }
5039
- }
5040
- await super.initialize();
5041
- this._setupThemeListener();
5042
- }
5043
- /**
5044
- * Get current theme.
5045
- */
5046
- getTheme() {
5047
- if (this._gemini?.ui?.getTheme) {
5048
- const theme = this._gemini.ui.getTheme();
5049
- return theme === "dark" ? "dark" : "light";
5050
- }
5051
- return super.getTheme();
5052
- }
5053
- /**
5054
- * Send a message (if supported by SDK).
5055
- */
5056
- async sendMessage(content) {
5057
- if (this._gemini?.ui?.sendMessage) {
5058
- await this._gemini.ui.sendMessage(content);
5059
- return;
5060
- }
5061
- throw new Error("Sending messages is not supported by Gemini adapter");
5062
- }
5063
- /**
5064
- * Open a link.
5065
- */
5066
- async openLink(url) {
5067
- if (this._gemini?.ui?.openLink) {
5068
- await this._gemini.ui.openLink(url);
5069
- return;
5070
- }
5071
- return super.openLink(url);
5072
- }
5073
- // ============================================
5074
- // Private Helpers
5075
- // ============================================
5076
- /**
5077
- * Setup listener for system theme changes.
5078
- */
5079
- _setupThemeListener() {
5080
- if (typeof window === "undefined" || !window.matchMedia) return;
5081
- const mediaQuery = window.matchMedia("(prefers-color-scheme: dark)");
5082
- const handleChange = (e) => {
5083
- if (!this._gemini?.ui?.getTheme) {
5084
- const newTheme = e.matches ? "dark" : "light";
5085
- if (newTheme !== this._hostContext.theme) {
5086
- this._notifyContextChange({ theme: newTheme });
5087
- }
5088
- }
5089
- };
5090
- if (mediaQuery.addEventListener) {
5091
- mediaQuery.addEventListener("change", handleChange);
5092
- } else if (mediaQuery.addListener) {
5093
- mediaQuery.addListener(handleChange);
5094
- }
5095
- }
5096
- };
5097
- function createGeminiAdapter() {
5098
- return new GeminiAdapter();
5099
- }
5100
-
5101
- // libs/ui/src/bridge/adapters/generic.adapter.ts
5102
- var GenericAdapter = class extends BaseAdapter {
5103
- id = "generic";
5104
- name = "Generic Web";
5105
- priority = 0;
5106
- // Lowest priority - fallback only
5107
- constructor() {
5108
- super();
5109
- this._capabilities = {
5110
- ...DEFAULT_CAPABILITIES,
5111
- canCallTools: false,
5112
- canSendMessages: false,
5113
- canOpenLinks: true,
5114
- // window.open works
5115
- canPersistState: true,
5116
- // localStorage works
5117
- hasNetworkAccess: true,
5118
- // Assume network available
5119
- supportsDisplayModes: false,
5120
- supportsTheme: true
5121
- // System theme detection
5122
- };
5123
- }
5124
- /**
5125
- * Generic adapter can always handle the environment.
5126
- * It serves as the fallback when no other adapter matches.
5127
- */
5128
- canHandle() {
5129
- return typeof window !== "undefined";
5130
- }
5131
- /**
5132
- * Initialize the generic adapter.
5133
- */
5134
- async initialize() {
5135
- if (this._initialized) return;
5136
- await super.initialize();
5137
- this._setupThemeListener();
5138
- }
5139
- /**
5140
- * Open a link using window.open.
5141
- */
5142
- async openLink(url) {
5143
- if (typeof window !== "undefined") {
5144
- window.open(url, "_blank", "noopener,noreferrer");
5145
- }
5146
- }
5147
- // ============================================
5148
- // Private Helpers
5149
- // ============================================
5150
- /**
5151
- * Setup listener for system theme changes.
5152
- */
5153
- _setupThemeListener() {
5154
- if (typeof window === "undefined" || !window.matchMedia) return;
5155
- const mediaQuery = window.matchMedia("(prefers-color-scheme: dark)");
5156
- const handleChange = (e) => {
5157
- const newTheme = e.matches ? "dark" : "light";
5158
- if (newTheme !== this._hostContext.theme) {
5159
- this._notifyContextChange({ theme: newTheme });
5160
- }
5161
- };
5162
- if (mediaQuery.addEventListener) {
5163
- mediaQuery.addEventListener("change", handleChange);
5164
- } else if (mediaQuery.addListener) {
5165
- mediaQuery.addListener(handleChange);
5166
- }
5167
- }
5168
- };
5169
- function createGenericAdapter() {
5170
- return new GenericAdapter();
5171
- }
5172
-
5173
- // libs/ui/src/bridge/adapters/index.ts
5174
- function registerBuiltInAdapters() {
5175
- defaultRegistry.register("openai", createOpenAIAdapter);
5176
- defaultRegistry.register("ext-apps", createExtAppsAdapter);
5177
- defaultRegistry.register("claude", createClaudeAdapter);
5178
- defaultRegistry.register("gemini", createGeminiAdapter);
5179
- defaultRegistry.register("generic", createGenericAdapter);
5180
- }
5181
- registerBuiltInAdapters();
5182
-
5183
- // libs/ui/src/bridge/runtime/iife-generator.ts
5184
- function generateBridgeIIFE(options = {}) {
5185
- const { debug = false, trustedOrigins = [], minify = false } = options;
5186
- const adapters = options.adapters || ["openai", "ext-apps", "claude", "gemini", "generic"];
5187
- const parts = [];
5188
- parts.push("(function() {");
5189
- parts.push('"use strict";');
5190
- parts.push("");
5191
- if (debug) {
5192
- parts.push('function log(msg) { console.log("[FrontMcpBridge] " + msg); }');
5193
- } else {
5194
- parts.push("function log() {}");
5195
- }
5196
- parts.push("");
5197
- parts.push("var DEFAULT_SAFE_AREA = { top: 0, bottom: 0, left: 0, right: 0 };");
5198
- parts.push("");
5199
- parts.push(generateContextDetection());
5200
- parts.push("");
5201
- parts.push(generateBaseCapabilities());
5202
- parts.push("");
5203
- if (adapters.includes("openai")) {
5204
- parts.push(generateOpenAIAdapter());
5205
- parts.push("");
5206
- }
5207
- if (adapters.includes("ext-apps")) {
5208
- parts.push(generateExtAppsAdapter(trustedOrigins));
5209
- parts.push("");
5210
- }
5211
- if (adapters.includes("claude")) {
5212
- parts.push(generateClaudeAdapter());
5213
- parts.push("");
5214
- }
5215
- if (adapters.includes("gemini")) {
5216
- parts.push(generateGeminiAdapter());
5217
- parts.push("");
5218
- }
5219
- if (adapters.includes("generic")) {
5220
- parts.push(generateGenericAdapter());
5221
- parts.push("");
5222
- }
5223
- parts.push(generatePlatformDetection(adapters));
5224
- parts.push("");
5225
- parts.push(generateBridgeClass());
5226
- parts.push("");
5227
- parts.push("var bridge = new FrontMcpBridge();");
5228
- parts.push("bridge.initialize().then(function() {");
5229
- parts.push(' log("Bridge initialized with adapter: " + bridge.adapterId);');
5230
- parts.push(' window.dispatchEvent(new CustomEvent("bridge:ready", { detail: { adapter: bridge.adapterId } }));');
5231
- parts.push("}).catch(function(err) {");
5232
- parts.push(' console.error("[FrontMcpBridge] Init failed:", err);');
5233
- parts.push(' window.dispatchEvent(new CustomEvent("bridge:error", { detail: { error: err } }));');
5234
- parts.push("});");
5235
- parts.push("");
5236
- parts.push("window.FrontMcpBridge = bridge;");
5237
- parts.push("})();");
5238
- const code = parts.join("\n");
5239
- if (minify) {
5240
- return minifyJS(code);
5241
- }
5242
- return code;
5243
- }
5244
- function generateContextDetection() {
5245
- return `
5246
- function detectTheme() {
5247
- if (typeof window !== 'undefined' && window.matchMedia) {
5248
- return window.matchMedia('(prefers-color-scheme: dark)').matches ? 'dark' : 'light';
5249
- }
5250
- return 'light';
5251
- }
5252
-
5253
- function detectLocale() {
5254
- if (typeof navigator !== 'undefined') {
5255
- return navigator.language || 'en-US';
5256
- }
5257
- return 'en-US';
5258
- }
5259
-
5260
- function detectUserAgent() {
5261
- if (typeof navigator === 'undefined') {
5262
- return { type: 'web', hover: true, touch: false };
5263
- }
5264
- var ua = navigator.userAgent || '';
5265
- var isMobile = /iPhone|iPad|iPod|Android/i.test(ua);
5266
- var hasTouch = 'ontouchstart' in window || navigator.maxTouchPoints > 0;
5267
- var hasHover = window.matchMedia && window.matchMedia('(hover: hover)').matches;
5268
- return { type: isMobile ? 'mobile' : 'web', hover: hasHover !== false, touch: hasTouch };
5269
- }
5270
-
5271
- function detectViewport() {
5272
- if (typeof window !== 'undefined') {
5273
- return { width: window.innerWidth, height: window.innerHeight };
5274
- }
5275
- return undefined;
5276
- }
5277
-
5278
- function readInjectedData() {
5279
- var data = { toolInput: {}, toolOutput: undefined, structuredContent: undefined };
5280
- if (typeof window !== 'undefined') {
5281
- if (window.__mcpToolInput) data.toolInput = window.__mcpToolInput;
5282
- if (window.__mcpToolOutput) data.toolOutput = window.__mcpToolOutput;
5283
- if (window.__mcpStructuredContent) data.structuredContent = window.__mcpStructuredContent;
5284
- }
5285
- return data;
5286
- }
5287
- `.trim();
5288
- }
5289
- function generateBaseCapabilities() {
5290
- return `
5291
- var DEFAULT_CAPABILITIES = {
5292
- canCallTools: false,
5293
- canSendMessages: false,
5294
- canOpenLinks: false,
5295
- canPersistState: true,
5296
- hasNetworkAccess: true,
5297
- supportsDisplayModes: false,
5298
- supportsTheme: true
5299
- };
5300
- `.trim();
5301
- }
5302
- function generateOpenAIAdapter() {
5303
- return `
5304
- var OpenAIAdapter = {
5305
- id: 'openai',
5306
- name: 'OpenAI ChatGPT',
5307
- priority: 100,
5308
- capabilities: Object.assign({}, DEFAULT_CAPABILITIES, {
5309
- canCallTools: true,
5310
- canSendMessages: true,
5311
- canOpenLinks: true,
5312
- supportsDisplayModes: true
5313
- }),
5314
- canHandle: function() {
5315
- if (typeof window === 'undefined') return false;
5316
- // Check for window.openai.callTool (the actual OpenAI SDK API)
5317
- if (window.openai && typeof window.openai.callTool === 'function') return true;
5318
- // Also check if we're being injected with tool metadata (OpenAI injects toolOutput)
5319
- if (window.openai && (window.openai.toolOutput !== undefined || window.openai.toolInput !== undefined)) return true;
5320
- return false;
5321
- },
5322
- initialize: function(context) {
5323
- var sdk = window.openai;
5324
- context.sdk = sdk;
5325
- // OpenAI SDK exposes theme and displayMode directly as properties
5326
- if (sdk.theme) {
5327
- context.hostContext.theme = sdk.theme;
5328
- }
5329
- if (sdk.displayMode) {
5330
- context.hostContext.displayMode = sdk.displayMode;
5331
- }
5332
- // Note: OpenAI SDK does not have an onContextChange equivalent
5333
- return Promise.resolve();
5334
- },
5335
- callTool: function(context, name, args) {
5336
- return context.sdk.callTool(name, args);
5337
- },
5338
- sendMessage: function(context, content) {
5339
- if (typeof context.sdk.sendFollowUpMessage === 'function') {
5340
- return context.sdk.sendFollowUpMessage(content);
5341
- }
5342
- return Promise.reject(new Error('Messages not supported'));
5343
- },
5344
- openLink: function(context, url) {
5345
- window.open(url, '_blank', 'noopener,noreferrer');
5346
- return Promise.resolve();
5347
- },
5348
- requestDisplayMode: function(context, mode) {
5349
- return Promise.resolve();
5350
- },
5351
- requestClose: function(context) {
5352
- return Promise.resolve();
5353
- }
5354
- };
5355
- `.trim();
5356
- }
5357
- function generateExtAppsAdapter(trustedOrigins) {
5358
- const originsArray = trustedOrigins.length > 0 ? JSON.stringify(trustedOrigins) : "[]";
5359
- return `
5360
- var ExtAppsAdapter = {
5361
- id: 'ext-apps',
5362
- name: 'ext-apps (SEP-1865)',
5363
- priority: 80,
5364
- capabilities: Object.assign({}, DEFAULT_CAPABILITIES, { canPersistState: true, hasNetworkAccess: true }),
5365
- trustedOrigins: ${originsArray},
5366
- trustedOrigin: null,
5367
- pendingRequests: {},
5368
- requestId: 0,
5369
- hostCapabilities: {},
5370
- canHandle: function() {
5371
- if (typeof window === 'undefined') return false;
5372
- if (window.parent === window) return false;
5373
- // Check for OpenAI SDK (window.openai.callTool) - defer to OpenAIAdapter
5374
- if (window.openai && typeof window.openai.callTool === 'function') return false;
5375
- if (window.__mcpPlatform === 'ext-apps') return true;
5376
- return true;
5377
- },
5378
- initialize: function(context) {
5379
- var self = this;
5380
- context.extApps = this;
5381
-
5382
- window.addEventListener('message', function(event) {
5383
- self.handleMessage(context, event);
5384
- });
5385
-
5386
- return self.performHandshake(context);
5387
- },
5388
- handleMessage: function(context, event) {
5389
- if (!this.isOriginTrusted(event.origin)) return;
5390
- var data = event.data;
5391
- if (!data || typeof data !== 'object' || data.jsonrpc !== '2.0') return;
5392
-
5393
- if ('id' in data && (data.result !== undefined || data.error !== undefined)) {
5394
- var pending = this.pendingRequests[data.id];
5395
- if (pending) {
5396
- clearTimeout(pending.timeout);
5397
- delete this.pendingRequests[data.id];
5398
- if (data.error) {
5399
- pending.reject(new Error(data.error.message + ' (code: ' + data.error.code + ')'));
5400
- } else {
5401
- pending.resolve(data.result);
5402
- }
5403
- }
5404
- return;
5405
- }
5406
-
5407
- if ('method' in data && !('id' in data)) {
5408
- this.handleNotification(context, data);
5409
- }
5410
- },
5411
- handleNotification: function(context, notification) {
5412
- var params = notification.params || {};
5413
- switch (notification.method) {
5414
- case 'ui/notifications/tool-input':
5415
- context.toolInput = params.arguments || {};
5416
- window.dispatchEvent(new CustomEvent('tool:input', { detail: { arguments: context.toolInput } }));
5417
- break;
5418
- case 'ui/notifications/tool-result':
5419
- context.toolOutput = params.content;
5420
- context.structuredContent = params.structuredContent;
5421
- context.notifyToolResult(params.content);
5422
- window.dispatchEvent(new CustomEvent('tool:result', { detail: params }));
5423
- break;
5424
- case 'ui/notifications/host-context-changed':
5425
- Object.assign(context.hostContext, params);
5426
- context.notifyContextChange(params);
5427
- break;
5428
- }
5429
- },
5430
- isOriginTrusted: function(origin) {
5431
- if (this.trustedOrigins.length > 0) {
5432
- return this.trustedOrigins.indexOf(origin) !== -1;
5433
- }
5434
- // When no trusted origins configured, only trust first message in iframe context
5435
- // This helps mitigate race conditions where a malicious iframe could establish trust
5436
- if (!this.trustedOrigin) {
5437
- if (window.parent !== window && origin) {
5438
- this.trustedOrigin = origin;
5439
- return true;
5440
- }
5441
- return false;
5442
- }
5443
- return this.trustedOrigin === origin;
5444
- },
5445
- sendRequest: function(method, params) {
5446
- var self = this;
5447
- return new Promise(function(resolve, reject) {
5448
- // Security: Require trusted origin before sending requests to prevent message leaks
5449
- if (!self.trustedOrigin && self.trustedOrigins.length === 0) {
5450
- reject(new Error('Cannot send request: no trusted origin established'));
5451
- return;
5452
- }
5453
-
5454
- var id = ++self.requestId;
5455
- var timeout = setTimeout(function() {
5456
- delete self.pendingRequests[id];
5457
- reject(new Error('Request ' + method + ' timed out'));
5458
- }, 10000);
5459
-
5460
- self.pendingRequests[id] = { resolve: resolve, reject: reject, timeout: timeout };
5461
-
5462
- var targetOrigin = self.trustedOrigin || self.trustedOrigins[0];
5463
- window.parent.postMessage({ jsonrpc: '2.0', id: id, method: method, params: params }, targetOrigin);
5464
- });
5465
- },
5466
- performHandshake: function(context) {
5467
- var self = this;
5468
- var params = {
5469
- appInfo: { name: 'FrontMCP Widget', version: '1.0.0' },
5470
- appCapabilities: { tools: { listChanged: false } },
5471
- protocolVersion: '2024-11-05'
5472
- };
5473
-
5474
- return this.sendRequest('ui/initialize', params).then(function(result) {
5475
- self.hostCapabilities = result.hostCapabilities || {};
5476
- self.capabilities = Object.assign({}, self.capabilities, {
5477
- canCallTools: Boolean(self.hostCapabilities.serverToolProxy),
5478
- canSendMessages: true,
5479
- canOpenLinks: Boolean(self.hostCapabilities.openLink),
5480
- supportsDisplayModes: true
5481
- });
5482
- if (result.hostContext) {
5483
- Object.assign(context.hostContext, result.hostContext);
5484
- }
5485
- });
5486
- },
5487
- callTool: function(context, name, args) {
5488
- if (!this.hostCapabilities.serverToolProxy) {
5489
- return Promise.reject(new Error('Server tool proxy not supported'));
5490
- }
5491
- return this.sendRequest('ui/callServerTool', { name: name, arguments: args });
5492
- },
5493
- sendMessage: function(context, content) {
5494
- return this.sendRequest('ui/message', { content: content });
5495
- },
5496
- openLink: function(context, url) {
5497
- if (!this.hostCapabilities.openLink) {
5498
- window.open(url, '_blank', 'noopener,noreferrer');
5499
- return Promise.resolve();
5500
- }
5501
- return this.sendRequest('ui/openLink', { url: url });
5502
- },
5503
- requestDisplayMode: function(context, mode) {
5504
- return this.sendRequest('ui/setDisplayMode', { mode: mode });
5505
- },
5506
- requestClose: function(context) {
5507
- return this.sendRequest('ui/close', {});
5508
- }
5509
- };
5510
- `.trim();
5511
- }
5512
- function generateClaudeAdapter() {
5513
- return `
5514
- var ClaudeAdapter = {
5515
- id: 'claude',
5516
- name: 'Claude (Anthropic)',
5517
- priority: 60,
5518
- capabilities: Object.assign({}, DEFAULT_CAPABILITIES, {
5519
- canCallTools: false,
5520
- canSendMessages: false,
5521
- canOpenLinks: true,
5522
- hasNetworkAccess: false,
5523
- supportsDisplayModes: false
5524
- }),
5525
- canHandle: function() {
5526
- if (typeof window === 'undefined') return false;
5527
- if (window.__mcpPlatform === 'claude') return true;
5528
- if (window.claude) return true;
5529
- if (window.__claudeArtifact) return true;
5530
- if (typeof location !== 'undefined') {
5531
- var href = location.href;
5532
- if (href.indexOf('claude.ai') !== -1 || href.indexOf('anthropic.com') !== -1) return true;
5533
- }
5534
- return false;
5535
- },
5536
- initialize: function(context) {
5537
- return Promise.resolve();
5538
- },
5539
- callTool: function() {
5540
- return Promise.reject(new Error('Tool calls not supported in Claude'));
5541
- },
5542
- sendMessage: function() {
5543
- return Promise.reject(new Error('Messages not supported in Claude'));
5544
- },
5545
- openLink: function(context, url) {
5546
- window.open(url, '_blank', 'noopener,noreferrer');
5547
- return Promise.resolve();
5548
- },
5549
- requestDisplayMode: function() {
5550
- return Promise.resolve();
5551
- },
5552
- requestClose: function() {
5553
- return Promise.resolve();
5554
- }
5555
- };
5556
- `.trim();
5557
- }
5558
- function generateGeminiAdapter() {
5559
- return `
5560
- var GeminiAdapter = {
5561
- id: 'gemini',
5562
- name: 'Google Gemini',
5563
- priority: 40,
5564
- capabilities: Object.assign({}, DEFAULT_CAPABILITIES, {
5565
- canOpenLinks: true,
5566
- hasNetworkAccess: true
5567
- }),
5568
- canHandle: function() {
5569
- if (typeof window === 'undefined') return false;
5570
- if (window.__mcpPlatform === 'gemini') return true;
5571
- if (window.gemini) return true;
5572
- if (typeof location !== 'undefined') {
5573
- var href = location.href;
5574
- if (href.indexOf('gemini.google.com') !== -1 || href.indexOf('bard.google.com') !== -1) return true;
5575
- }
5576
- return false;
5577
- },
5578
- initialize: function(context) {
5579
- if (window.gemini && window.gemini.ui && window.gemini.ui.getTheme) {
5580
- context.hostContext.theme = window.gemini.ui.getTheme() === 'dark' ? 'dark' : 'light';
5581
- }
5582
- return Promise.resolve();
5583
- },
5584
- callTool: function() {
5585
- return Promise.reject(new Error('Tool calls not supported in Gemini'));
5586
- },
5587
- sendMessage: function(context, content) {
5588
- if (window.gemini && window.gemini.ui && window.gemini.ui.sendMessage) {
5589
- return window.gemini.ui.sendMessage(content);
5590
- }
5591
- return Promise.reject(new Error('Messages not supported in Gemini'));
5592
- },
5593
- openLink: function(context, url) {
5594
- if (window.gemini && window.gemini.ui && window.gemini.ui.openLink) {
5595
- return window.gemini.ui.openLink(url);
5596
- }
5597
- window.open(url, '_blank', 'noopener,noreferrer');
5598
- return Promise.resolve();
5599
- },
5600
- requestDisplayMode: function() {
5601
- return Promise.resolve();
5602
- },
5603
- requestClose: function() {
5604
- return Promise.resolve();
3138
+ // Events (delegate to adapter)
3139
+ // ============================================
3140
+ /**
3141
+ * Subscribe to host context changes.
3142
+ * @param callback - Called when context changes
3143
+ * @returns Unsubscribe function
3144
+ */
3145
+ onContextChange(callback) {
3146
+ const adapter = this._ensureInitialized();
3147
+ return adapter.onContextChange(callback);
5605
3148
  }
5606
- };
5607
- `.trim();
5608
- }
5609
- function generateGenericAdapter() {
5610
- return `
5611
- var GenericAdapter = {
5612
- id: 'generic',
5613
- name: 'Generic Web',
5614
- priority: 0,
5615
- capabilities: Object.assign({}, DEFAULT_CAPABILITIES, {
5616
- canOpenLinks: true,
5617
- hasNetworkAccess: true
5618
- }),
5619
- canHandle: function() {
5620
- return typeof window !== 'undefined';
5621
- },
5622
- initialize: function(context) {
5623
- return Promise.resolve();
5624
- },
5625
- callTool: function() {
5626
- return Promise.reject(new Error('Tool calls not supported'));
5627
- },
5628
- sendMessage: function() {
5629
- return Promise.reject(new Error('Messages not supported'));
5630
- },
5631
- openLink: function(context, url) {
5632
- window.open(url, '_blank', 'noopener,noreferrer');
5633
- return Promise.resolve();
5634
- },
5635
- requestDisplayMode: function() {
5636
- return Promise.resolve();
5637
- },
5638
- requestClose: function() {
5639
- return Promise.resolve();
3149
+ /**
3150
+ * Subscribe to tool result updates.
3151
+ * @param callback - Called when tool result is received
3152
+ * @returns Unsubscribe function
3153
+ */
3154
+ onToolResult(callback) {
3155
+ const adapter = this._ensureInitialized();
3156
+ return adapter.onToolResult(callback);
5640
3157
  }
5641
- };
5642
- `.trim();
5643
- }
5644
- function generatePlatformDetection(adapters) {
5645
- const adapterVars = adapters.map((a) => {
5646
- switch (a) {
5647
- case "openai":
5648
- return "OpenAIAdapter";
5649
- case "ext-apps":
5650
- return "ExtAppsAdapter";
5651
- case "claude":
5652
- return "ClaudeAdapter";
5653
- case "gemini":
5654
- return "GeminiAdapter";
5655
- case "generic":
5656
- return "GenericAdapter";
5657
- default:
5658
- return "";
5659
- }
5660
- }).filter(Boolean);
5661
- return `
5662
- var ADAPTERS = [${adapterVars.join(", ")}].sort(function(a, b) { return b.priority - a.priority; });
5663
-
5664
- function detectPlatform() {
5665
- for (var i = 0; i < ADAPTERS.length; i++) {
5666
- if (ADAPTERS[i].canHandle()) {
5667
- log('Detected platform: ' + ADAPTERS[i].id);
5668
- return ADAPTERS[i];
3158
+ // ============================================
3159
+ // Private Helpers
3160
+ // ============================================
3161
+ /**
3162
+ * Ensure the bridge is initialized before operations.
3163
+ * Returns the adapter for type-safe access.
3164
+ */
3165
+ _ensureInitialized() {
3166
+ if (!this._initialized || !this._adapter) {
3167
+ throw new Error("FrontMcpBridge is not initialized. Call initialize() first.");
5669
3168
  }
3169
+ return this._adapter;
5670
3170
  }
5671
- log('No platform detected, using generic');
5672
- return GenericAdapter;
5673
- }
5674
- `.trim();
5675
- }
5676
- function generateBridgeClass() {
5677
- return `
5678
- function FrontMcpBridge() {
5679
- this._adapter = null;
5680
- this._initialized = false;
5681
- this._context = {
5682
- hostContext: {
5683
- theme: detectTheme(),
5684
- displayMode: 'inline',
5685
- locale: detectLocale(),
5686
- userAgent: detectUserAgent(),
5687
- safeArea: DEFAULT_SAFE_AREA,
5688
- viewport: detectViewport()
5689
- },
5690
- toolInput: {},
5691
- toolOutput: undefined,
5692
- structuredContent: undefined,
5693
- widgetState: {},
5694
- contextListeners: [],
5695
- toolResultListeners: [],
5696
- notifyContextChange: function(changes) {
5697
- Object.assign(this.hostContext, changes);
5698
- for (var i = 0; i < this.contextListeners.length; i++) {
5699
- try { this.contextListeners[i](changes); } catch(e) {}
5700
- }
5701
- },
5702
- notifyToolResult: function(result) {
5703
- this.toolOutput = result;
5704
- for (var i = 0; i < this.toolResultListeners.length; i++) {
5705
- try { this.toolResultListeners[i](result); } catch(e) {}
3171
+ /**
3172
+ * Wrap a promise with a timeout.
3173
+ */
3174
+ _withTimeout(promise, ms) {
3175
+ return new Promise((resolve, reject) => {
3176
+ const timer = setTimeout(() => {
3177
+ reject(new Error(`Operation timed out after ${ms}ms`));
3178
+ }, ms);
3179
+ promise.then((result) => {
3180
+ clearTimeout(timer);
3181
+ resolve(result);
3182
+ }).catch((error) => {
3183
+ clearTimeout(timer);
3184
+ reject(error);
3185
+ });
3186
+ });
3187
+ }
3188
+ /**
3189
+ * Emit a bridge event via CustomEvent.
3190
+ */
3191
+ _emitEvent(type, payload) {
3192
+ if (typeof window !== "undefined" && typeof CustomEvent !== "undefined") {
3193
+ try {
3194
+ const event = new CustomEvent(type, { detail: payload });
3195
+ window.dispatchEvent(event);
3196
+ } catch {
5706
3197
  }
5707
3198
  }
5708
- };
5709
-
5710
- var injected = readInjectedData();
5711
- this._context.toolInput = injected.toolInput;
5712
- this._context.toolOutput = injected.toolOutput;
5713
- this._context.structuredContent = injected.structuredContent;
5714
-
5715
- this._loadWidgetState();
5716
- }
5717
-
5718
- FrontMcpBridge.prototype._loadWidgetState = function() {
5719
- try {
5720
- var key = 'frontmcp:widget:' + (window.__mcpToolName || 'unknown');
5721
- var stored = localStorage.getItem(key);
5722
- if (stored) this._context.widgetState = JSON.parse(stored);
5723
- } catch(e) {}
5724
- };
5725
-
5726
- FrontMcpBridge.prototype._saveWidgetState = function() {
5727
- try {
5728
- var key = 'frontmcp:widget:' + (window.__mcpToolName || 'unknown');
5729
- localStorage.setItem(key, JSON.stringify(this._context.widgetState));
5730
- } catch(e) {}
5731
- };
5732
-
5733
- FrontMcpBridge.prototype.initialize = function() {
5734
- if (this._initialized) return Promise.resolve();
5735
- var self = this;
5736
- this._adapter = detectPlatform();
5737
- return this._adapter.initialize(this._context).then(function() {
5738
- self._initialized = true;
5739
- // Set up the data-tool-call click handler after initialization
5740
- self._setupDataToolCallHandler();
5741
- });
5742
- };
5743
-
5744
- Object.defineProperty(FrontMcpBridge.prototype, 'initialized', { get: function() { return this._initialized; } });
5745
- Object.defineProperty(FrontMcpBridge.prototype, 'adapterId', { get: function() { return this._adapter ? this._adapter.id : undefined; } });
5746
- Object.defineProperty(FrontMcpBridge.prototype, 'capabilities', { get: function() { return this._adapter ? this._adapter.capabilities : DEFAULT_CAPABILITIES; } });
5747
-
5748
- FrontMcpBridge.prototype.getTheme = function() { return this._context.hostContext.theme; };
5749
- FrontMcpBridge.prototype.getDisplayMode = function() { return this._context.hostContext.displayMode; };
5750
- FrontMcpBridge.prototype.getToolInput = function() { return this._context.toolInput; };
5751
- FrontMcpBridge.prototype.getToolOutput = function() { return this._context.toolOutput; };
5752
- FrontMcpBridge.prototype.getStructuredContent = function() { return this._context.structuredContent; };
5753
- FrontMcpBridge.prototype.getWidgetState = function() { return this._context.widgetState; };
5754
- FrontMcpBridge.prototype.getHostContext = function() { return Object.assign({}, this._context.hostContext); };
5755
- FrontMcpBridge.prototype.hasCapability = function(cap) { return this._adapter && this._adapter.capabilities[cap] === true; };
5756
-
5757
- // Get tool response metadata (platform-agnostic)
5758
- // Used by inline mode widgets to detect when ui/html arrives
5759
- FrontMcpBridge.prototype.getToolResponseMetadata = function() {
5760
- // OpenAI injects toolResponseMetadata for widget-producing tools
5761
- if (typeof window !== 'undefined' && window.openai && window.openai.toolResponseMetadata) {
5762
- return window.openai.toolResponseMetadata;
5763
- }
5764
- // Claude (future support)
5765
- if (typeof window !== 'undefined' && window.claude && window.claude.toolResponseMetadata) {
5766
- return window.claude.toolResponseMetadata;
5767
- }
5768
- // FrontMCP direct injection (for testing/ext-apps)
5769
- if (typeof window !== 'undefined' && window.__mcpToolResponseMetadata) {
5770
- return window.__mcpToolResponseMetadata;
5771
- }
5772
- return null;
5773
- };
5774
-
5775
- // Subscribe to tool response metadata changes (for inline mode injection)
5776
- FrontMcpBridge.prototype.onToolResponseMetadata = function(callback) {
5777
- var self = this;
5778
- var called = false;
5779
-
5780
- // Check if already available
5781
- var existing = self.getToolResponseMetadata();
5782
- if (existing) {
5783
- called = true;
5784
- callback(existing);
5785
3199
  }
5786
-
5787
- // Set up property interceptors for OpenAI
5788
- if (typeof window !== 'undefined') {
5789
- // OpenAI: Intercept toolResponseMetadata assignment
5790
- if (!window.__frontmcpMetadataIntercepted) {
5791
- window.__frontmcpMetadataIntercepted = true;
5792
- window.__frontmcpMetadataCallbacks = [];
5793
-
5794
- // Create openai object if it doesn't exist
5795
- if (!window.openai) window.openai = {};
5796
-
5797
- var originalMetadata = window.openai.toolResponseMetadata;
5798
- Object.defineProperty(window.openai, 'toolResponseMetadata', {
5799
- get: function() { return originalMetadata; },
5800
- set: function(val) {
5801
- originalMetadata = val;
5802
- log('toolResponseMetadata set, notifying ' + window.__frontmcpMetadataCallbacks.length + ' listeners');
5803
- for (var i = 0; i < window.__frontmcpMetadataCallbacks.length; i++) {
5804
- try { window.__frontmcpMetadataCallbacks[i](val); } catch(e) {}
5805
- }
5806
- },
5807
- configurable: true
5808
- });
3200
+ /**
3201
+ * Log debug message if debugging is enabled.
3202
+ */
3203
+ _log(message) {
3204
+ if (this._config.debug) {
3205
+ console.log(`[FrontMcpBridge] ${message}`);
5809
3206
  }
5810
-
5811
- // Register callback wrapper (store reference for unsubscribe)
5812
- var wrapper = function(metadata) {
5813
- if (!called) {
5814
- called = true;
5815
- callback(metadata);
5816
- }
5817
- };
5818
- window.__frontmcpMetadataCallbacks.push(wrapper);
5819
-
5820
- // Return unsubscribe function that removes the wrapper (not the original callback)
5821
- return function() {
5822
- if (window.__frontmcpMetadataCallbacks) {
5823
- var idx = window.__frontmcpMetadataCallbacks.indexOf(wrapper);
5824
- if (idx !== -1) window.__frontmcpMetadataCallbacks.splice(idx, 1);
5825
- }
5826
- };
5827
- }
5828
-
5829
- // Return no-op unsubscribe for non-window environments
5830
- return function() {};
5831
- };
5832
-
5833
- FrontMcpBridge.prototype.callTool = function(name, args) {
5834
- // Priority 1: Direct OpenAI SDK call (most reliable in OpenAI iframe)
5835
- // This bypasses adapter abstraction for maximum compatibility
5836
- if (typeof window !== 'undefined' && window.openai && typeof window.openai.callTool === 'function') {
5837
- log('callTool: Using OpenAI SDK directly');
5838
- return window.openai.callTool(name, args);
5839
3207
  }
5840
-
5841
- // Priority 2: Use adapter (if initialized and supports tool calls)
5842
- if (this._adapter && this._adapter.capabilities && this._adapter.capabilities.canCallTools) {
5843
- log('callTool: Using adapter ' + this._adapter.id);
5844
- return this._adapter.callTool(this._context, name, args);
5845
- }
5846
-
5847
- // Not initialized or no tool support
5848
- if (!this._adapter) {
5849
- return Promise.reject(new Error('Bridge not initialized. Wait for bridge:ready event.'));
5850
- }
5851
- return Promise.reject(new Error('Tool calls not supported on this platform (' + this._adapter.id + ')'));
5852
- };
5853
-
5854
- FrontMcpBridge.prototype.sendMessage = function(content) {
5855
- if (!this._adapter) return Promise.reject(new Error('Not initialized'));
5856
- return this._adapter.sendMessage(this._context, content);
5857
- };
5858
-
5859
- FrontMcpBridge.prototype.openLink = function(url) {
5860
- if (!this._adapter) return Promise.reject(new Error('Not initialized'));
5861
- return this._adapter.openLink(this._context, url);
5862
- };
5863
-
5864
- FrontMcpBridge.prototype.requestDisplayMode = function(mode) {
5865
- if (!this._adapter) return Promise.reject(new Error('Not initialized'));
5866
- var self = this;
5867
- return this._adapter.requestDisplayMode(this._context, mode).then(function() {
5868
- self._context.hostContext.displayMode = mode;
5869
- });
5870
- };
5871
-
5872
- FrontMcpBridge.prototype.requestClose = function() {
5873
- if (!this._adapter) return Promise.reject(new Error('Not initialized'));
5874
- return this._adapter.requestClose(this._context);
5875
- };
5876
-
5877
- FrontMcpBridge.prototype.setWidgetState = function(state) {
5878
- Object.assign(this._context.widgetState, state);
5879
- this._saveWidgetState();
5880
- };
5881
-
5882
- FrontMcpBridge.prototype.onContextChange = function(callback) {
5883
- var listeners = this._context.contextListeners;
5884
- listeners.push(callback);
5885
- return function() {
5886
- var idx = listeners.indexOf(callback);
5887
- if (idx !== -1) listeners.splice(idx, 1);
5888
- };
5889
- };
5890
-
5891
- FrontMcpBridge.prototype.onToolResult = function(callback) {
5892
- var listeners = this._context.toolResultListeners;
5893
- listeners.push(callback);
5894
- return function() {
5895
- var idx = listeners.indexOf(callback);
5896
- if (idx !== -1) listeners.splice(idx, 1);
5897
- };
5898
- };
5899
-
5900
- // ==================== data-tool-call Click Handler ====================
5901
-
5902
- FrontMcpBridge.prototype._setupDataToolCallHandler = function() {
5903
- var self = this;
5904
-
5905
- document.addEventListener('click', function(e) {
5906
- // Find the closest element with data-tool-call attribute
5907
- var target = e.target;
5908
- while (target && target !== document) {
5909
- if (target.hasAttribute && target.hasAttribute('data-tool-call')) {
5910
- var toolName = target.getAttribute('data-tool-call');
5911
- var argsAttr = target.getAttribute('data-tool-args');
5912
- var args = {};
5913
-
5914
- try {
5915
- if (argsAttr) {
5916
- args = JSON.parse(argsAttr);
5917
- }
5918
- } catch (parseErr) {
5919
- console.error('[frontmcp] Failed to parse data-tool-args:', parseErr);
5920
- }
5921
-
5922
- log('data-tool-call clicked: ' + toolName);
5923
-
5924
- // Show loading state - save original content first
5925
- var originalContent = target.innerHTML;
5926
- var originalDisabled = target.disabled;
5927
- target.disabled = true;
5928
- target.classList.add('opacity-50', 'cursor-not-allowed');
5929
-
5930
- // Add spinner for buttons
5931
- 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>';
5932
- if (target.tagName === 'BUTTON') {
5933
- target.innerHTML = spinner + 'Loading...';
5934
- }
5935
-
5936
- // Helper to reset button state
5937
- function resetButton() {
5938
- target.innerHTML = originalContent;
5939
- target.disabled = originalDisabled;
5940
- target.classList.remove('opacity-50', 'cursor-not-allowed');
5941
- }
5942
-
5943
- // Determine how to call the tool
5944
- var toolCallPromise;
5945
-
5946
- // Priority 1: Direct OpenAI SDK call (bypasses adapter abstraction)
5947
- if (typeof window !== 'undefined' && window.openai && typeof window.openai.callTool === 'function') {
5948
- log('Using OpenAI SDK directly for tool call');
5949
- toolCallPromise = window.openai.callTool(toolName, args);
5950
- }
5951
- // Priority 2: Use adapter (if it supports tool calls)
5952
- else if (self.hasCapability('canCallTools')) {
5953
- log('Using adapter for tool call');
5954
- toolCallPromise = self.callTool(toolName, args);
5955
- }
5956
- // No tool call capability
5957
- else {
5958
- console.error('[frontmcp] Tool calls not supported on this platform (' + self.adapterId + ')');
5959
- resetButton();
5960
- target.dispatchEvent(new CustomEvent('tool:error', {
5961
- detail: { name: toolName, args: args, error: 'Tool calls not supported on this platform' },
5962
- bubbles: true
5963
- }));
5964
- e.preventDefault();
5965
- return;
5966
- }
5967
-
5968
- // Handle the tool call result
5969
- toolCallPromise.then(function(result) {
5970
- log('Tool call succeeded: ' + toolName);
5971
- resetButton();
5972
-
5973
- // Update bridge state to trigger widget re-render
5974
- // React isn't hydrated in OpenAI iframe, so useState doesn't work
5975
- // Instead, we use the bridge's reactive state system
5976
- if (result && window.__frontmcp && window.__frontmcp.bridge && typeof window.__frontmcp.bridge.setWidgetState === 'function') {
5977
- var newData = result.structuredContent || result;
5978
- log('Updating bridge state with new data');
5979
- window.__frontmcp.bridge.setWidgetState(newData);
5980
- }
5981
-
5982
- // Dispatch success event
5983
- target.dispatchEvent(new CustomEvent('tool:success', {
5984
- detail: { name: toolName, args: args, result: result },
5985
- bubbles: true
5986
- }));
5987
- }).catch(function(err) {
5988
- console.error('[frontmcp] Tool call failed: ' + toolName, err);
5989
- resetButton();
5990
- // Dispatch error event
5991
- target.dispatchEvent(new CustomEvent('tool:error', {
5992
- detail: { name: toolName, args: args, error: err.message || err },
5993
- bubbles: true
5994
- }));
5995
- });
5996
-
5997
- // Prevent default behavior (e.g., form submission)
5998
- e.preventDefault();
5999
- return;
6000
- }
6001
- target = target.parentElement;
6002
- }
6003
- }, true); // Use capture phase to handle before React handlers
6004
3208
  };
6005
- `.trim();
6006
- }
6007
- function minifyJS(code) {
6008
- return code.replace(/\/\*[\s\S]*?\*\//g, "").replace(/\/\/.*$/gm, "").replace(/\s+/g, " ").replace(/\s*([{};,:()[\]])\s*/g, "$1").replace(/;\}/g, "}").trim();
6009
- }
6010
- function generatePlatformBundle(platform, options = {}) {
6011
- const platformAdapters = {
6012
- chatgpt: ["openai", "generic"],
6013
- claude: ["claude", "generic"],
6014
- gemini: ["gemini", "generic"],
6015
- universal: ["openai", "ext-apps", "claude", "gemini", "generic"]
6016
- };
6017
- return generateBridgeIIFE({
6018
- ...options,
6019
- adapters: platformAdapters[platform]
6020
- });
3209
+ async function createBridge(config, registry) {
3210
+ const bridge = new FrontMcpBridge(config, registry);
3211
+ await bridge.initialize();
3212
+ return bridge;
6021
3213
  }
6022
- var UNIVERSAL_BRIDGE_SCRIPT = generateBridgeIIFE();
6023
- var BRIDGE_SCRIPT_TAGS = {
6024
- universal: `<script>${UNIVERSAL_BRIDGE_SCRIPT}</script>`,
6025
- chatgpt: `<script>${generatePlatformBundle("chatgpt")}</script>`,
6026
- claude: `<script>${generatePlatformBundle("claude")}</script>`,
6027
- gemini: `<script>${generatePlatformBundle("gemini")}</script>`
6028
- };
3214
+
3215
+ // libs/ui/src/bridge/runtime/index.ts
3216
+ import {
3217
+ generateBridgeIIFE,
3218
+ generatePlatformBundle,
3219
+ UNIVERSAL_BRIDGE_SCRIPT,
3220
+ BRIDGE_SCRIPT_TAGS
3221
+ } from "@frontmcp/uipack";
6029
3222
 
6030
3223
  // libs/ui/src/web-components/core/base-element.ts
6031
3224
  import { validationErrorBox } from "@frontmcp/uipack/validation";
@@ -7107,15 +4300,6 @@ function renderToStringSync(element) {
7107
4300
  const ReactDOMServer = __require("react-dom/server");
7108
4301
  return ReactDOMServer.renderToStaticMarkup(element);
7109
4302
  }
7110
- function isReactAvailable() {
7111
- try {
7112
- __require("react");
7113
- __require("react-dom/server");
7114
- return true;
7115
- } catch {
7116
- return false;
7117
- }
7118
- }
7119
4303
 
7120
4304
  // libs/ui/src/react/Card.tsx
7121
4305
  import { Fragment, jsx, jsxs } from "react/jsx-runtime";
@@ -7297,7 +4481,7 @@ import {
7297
4481
  getAlertVariantClasses,
7298
4482
  ALERT_BASE_CLASSES,
7299
4483
  ALERT_ICONS,
7300
- CLOSE_ICON as CLOSE_ICON2,
4484
+ CLOSE_ICON,
7301
4485
  cn as cn4
7302
4486
  } from "@frontmcp/uipack/styles";
7303
4487
  import { jsx as jsx4, jsxs as jsxs4 } from "react/jsx-runtime";
@@ -7333,7 +4517,7 @@ function Alert({
7333
4517
  className: "flex-shrink-0 ml-3 hover:opacity-70 transition-opacity",
7334
4518
  "aria-label": "Dismiss",
7335
4519
  onClick: onDismiss,
7336
- children: /* @__PURE__ */ jsx4("span", { dangerouslySetInnerHTML: { __html: CLOSE_ICON2 } })
4520
+ children: /* @__PURE__ */ jsx4("span", { dangerouslySetInnerHTML: { __html: CLOSE_ICON } })
7337
4521
  }
7338
4522
  )
7339
4523
  ] }) });
@@ -7470,7 +4654,7 @@ function useCapability(cap) {
7470
4654
  }
7471
4655
 
7472
4656
  // libs/ui/src/react/hooks/tools.tsx
7473
- import { useState as useState2, useCallback as useCallback2, useEffect as useEffect2 } from "react";
4657
+ import { useState as useState2, useCallback, useEffect as useEffect2 } from "react";
7474
4658
  function useToolInput() {
7475
4659
  const { bridge, ready } = useMcpBridgeContext();
7476
4660
  if (!ready || !bridge) {
@@ -7536,7 +4720,7 @@ function useCallTool(toolName, options = {}) {
7536
4720
  });
7537
4721
  }
7538
4722
  }, [toolName, resetOnToolChange]);
7539
- const reset = useCallback2(() => {
4723
+ const reset = useCallback(() => {
7540
4724
  setState({
7541
4725
  data: null,
7542
4726
  loading: false,
@@ -7544,7 +4728,7 @@ function useCallTool(toolName, options = {}) {
7544
4728
  called: false
7545
4729
  });
7546
4730
  }, []);
7547
- const callTool = useCallback2(
4731
+ const callTool = useCallback(
7548
4732
  async (args) => {
7549
4733
  if (!ready || !bridge) {
7550
4734
  const error = new Error("Bridge not initialized");
@@ -7594,7 +4778,7 @@ function useToolCalls(toolMap) {
7594
4778
  }
7595
4779
  return initial;
7596
4780
  });
7597
- const createCallFn = useCallback2(
4781
+ const createCallFn = useCallback(
7598
4782
  (key, toolName) => async (args) => {
7599
4783
  if (!bridge) {
7600
4784
  setStates((prev) => ({
@@ -7629,7 +4813,7 @@ function useToolCalls(toolMap) {
7629
4813
  },
7630
4814
  [bridge]
7631
4815
  );
7632
- const createResetFn = useCallback2(
4816
+ const createResetFn = useCallback(
7633
4817
  (key) => () => {
7634
4818
  setStates((prev) => ({
7635
4819
  ...prev,
@@ -7663,7 +4847,7 @@ function useSendMessage() {
7663
4847
  error: null,
7664
4848
  sent: false
7665
4849
  });
7666
- const sendMessage = useCallback2(
4850
+ const sendMessage = useCallback(
7667
4851
  async (content) => {
7668
4852
  if (!bridge) {
7669
4853
  setState({ loading: false, error: new Error("Bridge not initialized"), sent: false });
@@ -7684,7 +4868,7 @@ function useSendMessage() {
7684
4868
  }
7685
4869
  function useOpenLink() {
7686
4870
  const bridge = useMcpBridge();
7687
- return useCallback2(
4871
+ return useCallback(
7688
4872
  async (url) => {
7689
4873
  if (!bridge) {
7690
4874
  console.warn("Bridge not initialized, cannot open link");
@@ -7696,58 +4880,19 @@ function useOpenLink() {
7696
4880
  );
7697
4881
  }
7698
4882
 
7699
- // libs/ui/src/react/utils.ts
7700
- import { escapeHtml as escapeHtml3 } from "@frontmcp/uipack/utils";
7701
- var cachedReactDOMServer = null;
7702
- function getReactDOMServer() {
7703
- if (!cachedReactDOMServer) {
7704
- try {
7705
- cachedReactDOMServer = __require("react-dom/server");
7706
- } catch {
7707
- return null;
7708
- }
7709
- }
7710
- return cachedReactDOMServer;
4883
+ // libs/ui/src/renderers/react.renderer.ts
4884
+ import { isReactComponent, containsJsx, hashString, transpileJsx } from "@frontmcp/uipack/renderers";
4885
+ var VALID_JS_IDENTIFIER = /^[a-zA-Z_$][a-zA-Z0-9_$]*$/;
4886
+ function isValidComponentName(name) {
4887
+ return VALID_JS_IDENTIFIER.test(name);
7711
4888
  }
7712
- function renderChildrenToString(children) {
7713
- if (children == null) {
7714
- return "";
7715
- }
7716
- if (typeof children === "string") {
7717
- return escapeHtml3(children);
7718
- }
7719
- if (typeof children === "number") {
7720
- return String(children);
7721
- }
7722
- if (typeof children === "boolean") {
7723
- return "";
7724
- }
7725
- try {
7726
- const server = getReactDOMServer();
7727
- if (server) {
7728
- return server.renderToStaticMarkup(children);
7729
- }
7730
- return String(children);
7731
- } catch {
7732
- return String(children);
4889
+ function sanitizeComponentName(name) {
4890
+ if (isValidComponentName(name)) {
4891
+ return name;
7733
4892
  }
4893
+ const sanitized = name.replace(/[^a-zA-Z0-9_$]/g, "_").replace(/^[0-9]/, "_$&");
4894
+ return sanitized || "Component";
7734
4895
  }
7735
- function isBrowser() {
7736
- return typeof window !== "undefined";
7737
- }
7738
- function isServer() {
7739
- return typeof window === "undefined";
7740
- }
7741
-
7742
- // libs/ui/src/renderers/react.renderer.ts
7743
- import {
7744
- isReactComponent,
7745
- containsJsx,
7746
- hashString,
7747
- transpileJsx,
7748
- executeTranspiledCode,
7749
- transpileCache
7750
- } from "@frontmcp/uipack/renderers";
7751
4896
  var REACT_CDN = {
7752
4897
  react: "https://esm.sh/react@19",
7753
4898
  reactDom: "https://esm.sh/react-dom@19/client"
@@ -7761,13 +4906,6 @@ var ReactRenderer = class {
7761
4906
  type = "react";
7762
4907
  priority = 20;
7763
4908
  // Higher priority than HTML
7764
- /**
7765
- * Lazy-loaded React modules.
7766
- */
7767
- // eslint-disable-next-line @typescript-eslint/no-explicit-any
7768
- React = null;
7769
- // eslint-disable-next-line @typescript-eslint/no-explicit-any
7770
- ReactDOMServer = null;
7771
4909
  /**
7772
4910
  * Check if this renderer can handle the given template.
7773
4911
  *
@@ -7808,46 +4946,109 @@ var ReactRenderer = class {
7808
4946
  throw new Error("Invalid template type for ReactRenderer");
7809
4947
  }
7810
4948
  /**
7811
- * Render the template to HTML string using react-dom/server.
4949
+ * Render the template to HTML for client-side rendering.
4950
+ *
4951
+ * Unlike SSR, this method generates HTML that will be rendered
4952
+ * client-side by React in the browser. No server-side React required.
4953
+ *
4954
+ * The generated HTML includes:
4955
+ * - A container div for the React root
4956
+ * - The component code (transpiled if needed)
4957
+ * - Props embedded as a data attribute
4958
+ * - A render script that initializes the component
7812
4959
  */
7813
- async render(template, context, options) {
7814
- await this.loadReact();
7815
- let Component;
4960
+ async render(template, context, _options) {
4961
+ const props = {
4962
+ input: context.input,
4963
+ output: context.output,
4964
+ structuredContent: context.structuredContent,
4965
+ helpers: context.helpers
4966
+ };
4967
+ const escapedProps = JSON.stringify(props).replace(/&/g, "&amp;").replace(/'/g, "&#39;").replace(/</g, "&lt;").replace(/>/g, "&gt;");
4968
+ const rootId = `frontmcp-react-${hashString(Date.now().toString()).slice(0, 8)}`;
4969
+ let componentCode;
4970
+ let componentName;
7816
4971
  if (typeof template === "function") {
7817
- Component = template;
4972
+ const rawName = template.name || "Component";
4973
+ componentName = sanitizeComponentName(rawName);
4974
+ componentCode = `
4975
+ // Component should be registered via window.__frontmcp_components['${componentName}']
4976
+ (function() {
4977
+ if (!window.__frontmcp_components || !window.__frontmcp_components['${componentName}']) {
4978
+ console.error('[FrontMCP] Component "${componentName}" not registered. Use buildHydrationScript() to register components.');
4979
+ }
4980
+ })();
4981
+ `;
7818
4982
  } else if (typeof template === "string") {
7819
4983
  const transpiled = await this.transpile(template);
7820
- const cached = transpileCache.getByKey(`exec:${transpiled.hash}`);
7821
- if (cached) {
7822
- Component = cached.code;
7823
- } else {
7824
- Component = await executeTranspiledCode(transpiled.code, {
7825
- // Provide any additional MDX components if specified
7826
- ...options?.mdxComponents
7827
- });
7828
- transpileCache.setByKey(`exec:${transpiled.hash}`, {
7829
- code: Component,
7830
- hash: transpiled.hash,
7831
- cached: false
7832
- });
7833
- }
4984
+ const match = transpiled.code.match(/function\s+(\w+)/);
4985
+ const rawName = match?.[1] || "Widget";
4986
+ componentName = sanitizeComponentName(rawName);
4987
+ componentCode = transpiled.code;
7834
4988
  } else {
7835
4989
  throw new Error("Invalid template type for ReactRenderer");
7836
4990
  }
7837
- const props = {
7838
- input: context.input,
7839
- output: context.output,
7840
- structuredContent: context.structuredContent,
7841
- helpers: context.helpers
4991
+ const html = `
4992
+ <div id="${rootId}" data-frontmcp-react data-component="${componentName}" data-props='${escapedProps}'>
4993
+ <div class="flex items-center justify-center p-4 text-gray-500">
4994
+ <svg class="animate-spin h-5 w-5 mr-2" viewBox="0 0 24 24">
4995
+ <circle class="opacity-25" cx="12" cy="12" r="10" stroke="currentColor" stroke-width="4" fill="none"></circle>
4996
+ <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>
4997
+ </svg>
4998
+ Loading...
4999
+ </div>
5000
+ </div>
5001
+ <script type="module">
5002
+ (function() {
5003
+ ${componentCode}
5004
+
5005
+ // Wait for React to be available
5006
+ function waitForReact(callback, maxAttempts) {
5007
+ var attempts = 0;
5008
+ var check = function() {
5009
+ if (typeof React !== 'undefined' && typeof ReactDOM !== 'undefined') {
5010
+ callback();
5011
+ } else if (attempts < maxAttempts) {
5012
+ attempts++;
5013
+ setTimeout(check, 50);
5014
+ } else {
5015
+ console.error('[FrontMCP] React not loaded after ' + maxAttempts + ' attempts');
5016
+ }
7842
5017
  };
7843
- const element = this.React.createElement(Component, props);
7844
- const html = this.ReactDOMServer.renderToString(element);
7845
- if (options?.hydrate) {
7846
- const componentName = Component.name || "Component";
7847
- const escapedProps = JSON.stringify(props).replace(/&/g, "&amp;").replace(/'/g, "&#39;").replace(/</g, "&lt;").replace(/>/g, "&gt;");
7848
- return `<div data-hydrate="${componentName}" data-props='${escapedProps}'>${html}</div>`;
5018
+ check();
5019
+ }
5020
+
5021
+ waitForReact(function() {
5022
+ try {
5023
+ var root = document.getElementById('${rootId}');
5024
+ if (!root) return;
5025
+
5026
+ var propsJson = root.getAttribute('data-props');
5027
+ var props = propsJson ? JSON.parse(propsJson.replace(/&amp;/g, '&').replace(/&#39;/g, "'").replace(/&lt;/g, '<').replace(/&gt;/g, '>')) : {};
5028
+
5029
+ // Get the component
5030
+ var Component = ${componentName};
5031
+
5032
+ // Check if it's registered globally
5033
+ if (typeof Component === 'undefined' && window.__frontmcp_components) {
5034
+ Component = window.__frontmcp_components['${componentName}'];
5035
+ }
5036
+
5037
+ if (typeof Component === 'function') {
5038
+ var element = React.createElement(Component, props);
5039
+ var reactRoot = ReactDOM.createRoot(root);
5040
+ reactRoot.render(element);
5041
+ } else {
5042
+ console.error('[FrontMCP] Component "${componentName}" not found');
5043
+ }
5044
+ } catch (err) {
5045
+ console.error('[FrontMCP] React render error:', err);
7849
5046
  }
7850
- return html;
5047
+ }, 100);
5048
+ })();
5049
+ </script>
5050
+ `;
5051
+ return html.trim();
7851
5052
  }
7852
5053
  /**
7853
5054
  * Get runtime scripts for client-side functionality.
@@ -7868,54 +5069,8 @@ var ReactRenderer = class {
7868
5069
  isInline: false
7869
5070
  };
7870
5071
  }
7871
- /**
7872
- * Load React and ReactDOMServer modules.
7873
- */
7874
- async loadReact() {
7875
- if (this.React && this.ReactDOMServer) {
7876
- return;
7877
- }
7878
- try {
7879
- this.React = await import("react");
7880
- this.ReactDOMServer = await import("react-dom/server");
7881
- } catch {
7882
- throw new Error("React is required for ReactRenderer. Install react and react-dom: npm install react react-dom");
7883
- }
7884
- }
7885
5072
  };
7886
5073
  var reactRenderer = new ReactRenderer();
7887
- function buildHydrationScript() {
7888
- return `
7889
- <script>
7890
- (function() {
7891
- // Wait for React to be available
7892
- if (typeof React === 'undefined' || typeof ReactDOM === 'undefined') {
7893
- console.warn('[FrontMCP] React not available for hydration');
7894
- return;
7895
- }
7896
-
7897
- // Find all elements marked for hydration
7898
- document.querySelectorAll('[data-hydrate]').forEach(function(root) {
7899
- var componentName = root.getAttribute('data-hydrate');
7900
- var propsJson = root.getAttribute('data-props');
7901
- var props = propsJson ? JSON.parse(propsJson) : {};
7902
-
7903
- // Look for the component in the global scope
7904
- if (window.__frontmcp_components && window.__frontmcp_components[componentName]) {
7905
- try {
7906
- ReactDOM.hydrateRoot(root, React.createElement(
7907
- window.__frontmcp_components[componentName],
7908
- props
7909
- ));
7910
- } catch (e) {
7911
- console.error('[FrontMCP] Hydration failed for', componentName, e);
7912
- }
7913
- }
7914
- });
7915
- })();
7916
- </script>
7917
- `;
7918
- }
7919
5074
 
7920
5075
  // libs/ui/src/renderers/react.adapter.ts
7921
5076
  var mountedRoots = /* @__PURE__ */ new WeakMap();
@@ -8204,22 +5359,15 @@ export {
8204
5359
  badgeGroup,
8205
5360
  baseLayout,
8206
5361
  betaBadge,
8207
- buildHydrationScript,
8208
5362
  busyDot,
8209
5363
  button,
8210
5364
  buttonGroup,
8211
5365
  card,
8212
5366
  cardGroup,
8213
5367
  checkbox,
8214
- circularProgress,
8215
- codePreview,
8216
5368
  confirmModal,
8217
- consentDeniedPage,
8218
5369
  consentLayout,
8219
5370
  consentLayoutBuilder,
8220
- consentPage,
8221
- consentSuccessPage,
8222
- contentSkeleton,
8223
5371
  createBridge,
8224
5372
  createLayoutBuilder,
8225
5373
  createReactAdapter,
@@ -8232,10 +5380,8 @@ export {
8232
5380
  errorBadge,
8233
5381
  errorLayout,
8234
5382
  errorLayoutBuilder,
8235
- errorPage,
8236
5383
  escapeHtml2 as escapeHtml,
8237
5384
  featureList,
8238
- forbiddenPage,
8239
5385
  form,
8240
5386
  formActions,
8241
5387
  formRow,
@@ -8244,33 +5390,23 @@ export {
8244
5390
  generatePlatformBundle,
8245
5391
  ghostButton,
8246
5392
  hiddenInput,
8247
- imagePreview,
8248
5393
  inactiveBadge,
8249
5394
  infoAlert,
8250
5395
  input,
8251
- isBrowser,
8252
- isReactAvailable,
8253
- isServer,
8254
5396
  linkButton,
8255
5397
  loadReactAdapter,
8256
5398
  loadingLayout,
8257
- maintenancePage,
8258
5399
  modal,
8259
5400
  modalTrigger,
8260
5401
  newBadge,
8261
- notFoundPage,
8262
- oauthErrorPage,
8263
5402
  offlineDot,
8264
- offlinePage,
8265
5403
  onlineDot,
8266
5404
  outlineButton,
8267
5405
  pagination,
8268
5406
  pendingBadge,
8269
5407
  permissionList,
8270
5408
  primaryButton,
8271
- progressBar,
8272
5409
  radioGroup,
8273
- rateLimitPage,
8274
5410
  reactRenderer,
8275
5411
  registerAdapter,
8276
5412
  registerAllComponents,
@@ -8288,26 +5424,14 @@ export {
8288
5424
  renderButtonSync,
8289
5425
  renderCard,
8290
5426
  renderCardSync,
8291
- renderChildrenToString,
8292
- renderToString,
8293
- renderToStringSync,
8294
- resourceItem,
8295
- resourceList,
8296
- resourceWidget,
8297
5427
  secondaryButton,
8298
5428
  select,
8299
- serverErrorPage,
8300
- sessionExpiredPage,
8301
- skeleton,
8302
- statusIndicator,
8303
- stepProgress,
8304
5429
  successAlert,
8305
5430
  successLayout,
8306
5431
  table,
8307
5432
  textarea,
8308
5433
  toast,
8309
5434
  toastContainer,
8310
- unauthorizedPage,
8311
5435
  useCallTool,
8312
5436
  useCapability,
8313
5437
  useDisplayMode,