@ulearning/ai-generative-frame 0.0.0-stage → 1.0.0-next.0

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 (323) hide show
  1. package/LICENSE +21 -0
  2. package/README.md +233 -2
  3. package/dist/assistant-ui/entry.d.ts +34 -0
  4. package/dist/assistant-ui/entry.d.ts.map +1 -0
  5. package/dist/assistant-ui/entry.js +50 -0
  6. package/dist/assistant-ui/entry.js.map +1 -0
  7. package/dist/assistant-ui/extension.d.ts +18 -0
  8. package/dist/assistant-ui/extension.d.ts.map +1 -0
  9. package/dist/assistant-ui/extension.js +0 -0
  10. package/dist/assistant-ui/history.d.ts +15 -0
  11. package/dist/assistant-ui/history.d.ts.map +1 -0
  12. package/dist/assistant-ui/history.js +39 -0
  13. package/dist/assistant-ui/history.js.map +1 -0
  14. package/dist/assistant-ui/modelContext.d.ts +45 -0
  15. package/dist/assistant-ui/modelContext.d.ts.map +1 -0
  16. package/dist/assistant-ui/modelContext.js +88 -0
  17. package/dist/assistant-ui/modelContext.js.map +1 -0
  18. package/dist/assistant-ui/snapshotSaver.d.ts +11 -0
  19. package/dist/assistant-ui/snapshotSaver.d.ts.map +1 -0
  20. package/dist/assistant-ui/snapshotSaver.js +83 -0
  21. package/dist/assistant-ui/snapshotSaver.js.map +1 -0
  22. package/dist/assistant-ui/tool-calls.d.ts +21 -0
  23. package/dist/assistant-ui/tool-calls.d.ts.map +1 -0
  24. package/dist/assistant-ui/tool-calls.js +65 -0
  25. package/dist/assistant-ui/tool-calls.js.map +1 -0
  26. package/dist/assistant-ui/toolkit.d.ts +96 -0
  27. package/dist/assistant-ui/toolkit.d.ts.map +1 -0
  28. package/dist/assistant-ui/toolkit.js +456 -0
  29. package/dist/assistant-ui/toolkit.js.map +1 -0
  30. package/dist/assistant-ui/useSharedContext.d.ts +14 -0
  31. package/dist/assistant-ui/useSharedContext.d.ts.map +1 -0
  32. package/dist/assistant-ui/useSharedContext.js +27 -0
  33. package/dist/assistant-ui/useSharedContext.js.map +1 -0
  34. package/dist/assistant-ui.d.ts +3 -0
  35. package/dist/assistant-ui.d.ts.map +1 -0
  36. package/dist/assistant-ui.js +1 -0
  37. package/dist/bootstrap.d.ts +23 -0
  38. package/dist/bootstrap.d.ts.map +1 -0
  39. package/dist/bootstrap.js +78 -0
  40. package/dist/bootstrap.js.map +1 -0
  41. package/dist/csp.d.ts +25 -0
  42. package/dist/csp.d.ts.map +1 -0
  43. package/dist/csp.js +78 -0
  44. package/dist/csp.js.map +1 -0
  45. package/dist/index.d.ts +6 -0
  46. package/dist/index.d.ts.map +1 -0
  47. package/dist/index.js +5 -0
  48. package/dist/json-schema.d.ts +70 -0
  49. package/dist/json-schema.d.ts.map +1 -0
  50. package/dist/json-schema.js +163 -0
  51. package/dist/json-schema.js.map +1 -0
  52. package/dist/preview.d.ts +30 -0
  53. package/dist/preview.d.ts.map +1 -0
  54. package/dist/preview.js +60 -0
  55. package/dist/preview.js.map +1 -0
  56. package/dist/prompts/guidance.d.ts +41 -0
  57. package/dist/prompts/guidance.d.ts.map +1 -0
  58. package/dist/prompts/guidance.js +268 -0
  59. package/dist/prompts/guidance.js.map +1 -0
  60. package/dist/prompts.d.ts +2 -0
  61. package/dist/prompts.d.ts.map +1 -0
  62. package/dist/prompts.js +1 -0
  63. package/dist/protocol.d.ts +114 -0
  64. package/dist/protocol.d.ts.map +1 -0
  65. package/dist/protocol.js +49 -0
  66. package/dist/protocol.js.map +1 -0
  67. package/dist/repair/repair.d.ts +72 -0
  68. package/dist/repair/repair.d.ts.map +1 -0
  69. package/dist/repair/repair.js +103 -0
  70. package/dist/repair/repair.js.map +1 -0
  71. package/dist/repair.d.ts +2 -0
  72. package/dist/repair.d.ts.map +1 -0
  73. package/dist/repair.js +1 -0
  74. package/dist/rpc.d.ts +60 -0
  75. package/dist/rpc.d.ts.map +1 -0
  76. package/dist/rpc.js +127 -0
  77. package/dist/rpc.js.map +1 -0
  78. package/dist/runtime/diagnostics.d.ts +17 -0
  79. package/dist/runtime/diagnostics.d.ts.map +1 -0
  80. package/dist/runtime/diagnostics.js +128 -0
  81. package/dist/runtime/diagnostics.js.map +1 -0
  82. package/dist/runtime/generated.d.ts +2 -0
  83. package/dist/runtime/generated.d.ts.map +1 -0
  84. package/dist/runtime/generated.js +5 -0
  85. package/dist/runtime/generated.js.map +1 -0
  86. package/dist/runtime/index.d.ts +7 -0
  87. package/dist/runtime/index.d.ts.map +1 -0
  88. package/dist/runtime/index.js +264 -0
  89. package/dist/runtime/index.js.map +1 -0
  90. package/dist/runtime/morph.d.ts +51 -0
  91. package/dist/runtime/morph.d.ts.map +1 -0
  92. package/dist/runtime/morph.js +251 -0
  93. package/dist/runtime/morph.js.map +1 -0
  94. package/dist/runtime/screenshot.d.ts +23 -0
  95. package/dist/runtime/screenshot.d.ts.map +1 -0
  96. package/dist/runtime/screenshot.js +188 -0
  97. package/dist/runtime/screenshot.js.map +1 -0
  98. package/dist/runtime/size.d.ts +10 -0
  99. package/dist/runtime/size.d.ts.map +1 -0
  100. package/dist/runtime/size.js +21 -0
  101. package/dist/runtime/size.js.map +1 -0
  102. package/dist/runtime/storage.d.ts +4 -0
  103. package/dist/runtime/storage.d.ts.map +1 -0
  104. package/dist/runtime/storage.js +38 -0
  105. package/dist/runtime/storage.js.map +1 -0
  106. package/dist/spec/actions.d.ts +40 -0
  107. package/dist/spec/actions.d.ts.map +1 -0
  108. package/dist/spec/actions.js +53 -0
  109. package/dist/spec/actions.js.map +1 -0
  110. package/dist/spec/assistant-ui/thread.d.ts +66 -0
  111. package/dist/spec/assistant-ui/thread.d.ts.map +1 -0
  112. package/dist/spec/assistant-ui/thread.js +252 -0
  113. package/dist/spec/assistant-ui/thread.js.map +1 -0
  114. package/dist/spec/assistant-ui/toolkit.d.ts +33 -0
  115. package/dist/spec/assistant-ui/toolkit.d.ts.map +1 -0
  116. package/dist/spec/assistant-ui/toolkit.js +142 -0
  117. package/dist/spec/assistant-ui/toolkit.js.map +1 -0
  118. package/dist/spec/assistant-ui/web.d.ts +6 -0
  119. package/dist/spec/assistant-ui/web.d.ts.map +1 -0
  120. package/dist/spec/assistant-ui/web.js +48 -0
  121. package/dist/spec/assistant-ui/web.js.map +1 -0
  122. package/dist/spec/assistant-ui.d.ts +3 -0
  123. package/dist/spec/assistant-ui.d.ts.map +1 -0
  124. package/dist/spec/assistant-ui.js +1 -0
  125. package/dist/spec/calc.d.ts +8 -0
  126. package/dist/spec/calc.d.ts.map +1 -0
  127. package/dist/spec/calc.js +498 -0
  128. package/dist/spec/calc.js.map +1 -0
  129. package/dist/spec/catalog.d.ts +52 -0
  130. package/dist/spec/catalog.d.ts.map +1 -0
  131. package/dist/spec/catalog.js +104 -0
  132. package/dist/spec/catalog.js.map +1 -0
  133. package/dist/spec/expressions.d.ts +45 -0
  134. package/dist/spec/expressions.d.ts.map +1 -0
  135. package/dist/spec/expressions.js +199 -0
  136. package/dist/spec/expressions.js.map +1 -0
  137. package/dist/spec/patch.d.ts +20 -0
  138. package/dist/spec/patch.d.ts.map +1 -0
  139. package/dist/spec/patch.js +77 -0
  140. package/dist/spec/patch.js.map +1 -0
  141. package/dist/spec/pending.d.ts +15 -0
  142. package/dist/spec/pending.d.ts.map +1 -0
  143. package/dist/spec/pending.js +33 -0
  144. package/dist/spec/pending.js.map +1 -0
  145. package/dist/spec/pointer.d.ts +17 -0
  146. package/dist/spec/pointer.d.ts.map +1 -0
  147. package/dist/spec/pointer.js +88 -0
  148. package/dist/spec/pointer.js.map +1 -0
  149. package/dist/spec/prompt.d.ts +29 -0
  150. package/dist/spec/prompt.d.ts.map +1 -0
  151. package/dist/spec/prompt.js +145 -0
  152. package/dist/spec/prompt.js.map +1 -0
  153. package/dist/spec/render-spec.d.ts +50 -0
  154. package/dist/spec/render-spec.d.ts.map +1 -0
  155. package/dist/spec/render-spec.js +104 -0
  156. package/dist/spec/render-spec.js.map +1 -0
  157. package/dist/spec/resolve.d.ts +39 -0
  158. package/dist/spec/resolve.d.ts.map +1 -0
  159. package/dist/spec/resolve.js +51 -0
  160. package/dist/spec/resolve.js.map +1 -0
  161. package/dist/spec/resolved.d.ts +3 -0
  162. package/dist/spec/resolved.d.ts.map +1 -0
  163. package/dist/spec/resolved.js +36 -0
  164. package/dist/spec/resolved.js.map +1 -0
  165. package/dist/spec/state.d.ts +34 -0
  166. package/dist/spec/state.d.ts.map +1 -0
  167. package/dist/spec/state.js +111 -0
  168. package/dist/spec/state.js.map +1 -0
  169. package/dist/spec/stream.d.ts +50 -0
  170. package/dist/spec/stream.d.ts.map +1 -0
  171. package/dist/spec/stream.js +168 -0
  172. package/dist/spec/stream.js.map +1 -0
  173. package/dist/spec/tools.d.ts +3 -0
  174. package/dist/spec/tools.d.ts.map +1 -0
  175. package/dist/spec/tools.js +3 -0
  176. package/dist/spec/tree.d.ts +21 -0
  177. package/dist/spec/tree.d.ts.map +1 -0
  178. package/dist/spec/tree.js +27 -0
  179. package/dist/spec/tree.js.map +1 -0
  180. package/dist/spec/types.d.ts +66 -0
  181. package/dist/spec/types.d.ts.map +1 -0
  182. package/dist/spec/types.js +9 -0
  183. package/dist/spec/types.js.map +1 -0
  184. package/dist/spec/validate.d.ts +32 -0
  185. package/dist/spec/validate.d.ts.map +1 -0
  186. package/dist/spec/validate.js +264 -0
  187. package/dist/spec/validate.js.map +1 -0
  188. package/dist/spec/vue/SpecRenderer.d.ts +43 -0
  189. package/dist/spec/vue/SpecRenderer.d.ts.map +1 -0
  190. package/dist/spec/vue/SpecRenderer.js +357 -0
  191. package/dist/spec/vue/SpecRenderer.js.map +1 -0
  192. package/dist/spec/vue/useSpecStream.d.ts +19 -0
  193. package/dist/spec/vue/useSpecStream.d.ts.map +1 -0
  194. package/dist/spec/vue/useSpecStream.js +115 -0
  195. package/dist/spec/vue/useSpecStream.js.map +1 -0
  196. package/dist/spec/vue.d.ts +3 -0
  197. package/dist/spec/vue.d.ts.map +1 -0
  198. package/dist/spec/vue.js +3 -0
  199. package/dist/spec.d.ts +12 -0
  200. package/dist/spec.d.ts.map +1 -0
  201. package/dist/spec.js +8 -0
  202. package/dist/theme.d.ts +38 -0
  203. package/dist/theme.d.ts.map +1 -0
  204. package/dist/theme.js +370 -0
  205. package/dist/theme.js.map +1 -0
  206. package/dist/tools/define.d.ts +43 -0
  207. package/dist/tools/define.d.ts.map +1 -0
  208. package/dist/tools/define.js +31 -0
  209. package/dist/tools/define.js.map +1 -0
  210. package/dist/tools/edits.d.ts +19 -0
  211. package/dist/tools/edits.d.ts.map +1 -0
  212. package/dist/tools/edits.js +55 -0
  213. package/dist/tools/edits.js.map +1 -0
  214. package/dist/tools/registry.d.ts +19 -0
  215. package/dist/tools/registry.d.ts.map +1 -0
  216. package/dist/tools/registry.js +33 -0
  217. package/dist/tools/registry.js.map +1 -0
  218. package/dist/tools/tools.d.ts +88 -0
  219. package/dist/tools/tools.d.ts.map +1 -0
  220. package/dist/tools/tools.js +235 -0
  221. package/dist/tools/tools.js.map +1 -0
  222. package/dist/tools.d.ts +5 -0
  223. package/dist/tools.d.ts.map +1 -0
  224. package/dist/tools.js +5 -0
  225. package/dist/version.d.ts +2 -0
  226. package/dist/version.d.ts.map +1 -0
  227. package/dist/version.js +5 -0
  228. package/dist/version.js.map +1 -0
  229. package/dist/vue/Widget.d.ts +16 -0
  230. package/dist/vue/Widget.d.ts.map +1 -0
  231. package/dist/vue/Widget.js +58 -0
  232. package/dist/vue/Widget.js.map +1 -0
  233. package/dist/vue/sync.d.ts +19 -0
  234. package/dist/vue/sync.d.ts.map +1 -0
  235. package/dist/vue/sync.js +31 -0
  236. package/dist/vue/sync.js.map +1 -0
  237. package/dist/vue/themeStore.d.ts +23 -0
  238. package/dist/vue/themeStore.d.ts.map +1 -0
  239. package/dist/vue/themeStore.js +115 -0
  240. package/dist/vue/themeStore.js.map +1 -0
  241. package/dist/vue/useThemeTokens.d.ts +6 -0
  242. package/dist/vue/useThemeTokens.d.ts.map +1 -0
  243. package/dist/vue/useThemeTokens.js +49 -0
  244. package/dist/vue/useThemeTokens.js.map +1 -0
  245. package/dist/vue/useWidget.d.ts +10 -0
  246. package/dist/vue/useWidget.d.ts.map +1 -0
  247. package/dist/vue/useWidget.js +81 -0
  248. package/dist/vue/useWidget.js.map +1 -0
  249. package/dist/vue.d.ts +5 -0
  250. package/dist/vue.d.ts.map +1 -0
  251. package/dist/vue.js +4 -0
  252. package/dist/widget.d.ts +129 -0
  253. package/dist/widget.d.ts.map +1 -0
  254. package/dist/widget.js +655 -0
  255. package/dist/widget.js.map +1 -0
  256. package/package.json +125 -3
  257. package/src/assistant-ui/entry.ts +124 -0
  258. package/src/assistant-ui/extension.ts +19 -0
  259. package/src/assistant-ui/history.ts +63 -0
  260. package/src/assistant-ui/modelContext.ts +162 -0
  261. package/src/assistant-ui/snapshotSaver.ts +116 -0
  262. package/src/assistant-ui/tool-calls.ts +115 -0
  263. package/src/assistant-ui/toolkit.tsx +747 -0
  264. package/src/assistant-ui/useSharedContext.ts +40 -0
  265. package/src/assistant-ui.ts +14 -0
  266. package/src/bootstrap.ts +112 -0
  267. package/src/csp.ts +96 -0
  268. package/src/index.ts +44 -0
  269. package/src/json-schema.ts +353 -0
  270. package/src/preview.ts +120 -0
  271. package/src/prompts/guidance.ts +369 -0
  272. package/src/prompts.ts +8 -0
  273. package/src/protocol.ts +141 -0
  274. package/src/repair/repair.ts +202 -0
  275. package/src/repair.ts +11 -0
  276. package/src/rpc.ts +228 -0
  277. package/src/runtime/diagnostics.ts +165 -0
  278. package/src/runtime/generated.ts +2 -0
  279. package/src/runtime/index.ts +343 -0
  280. package/src/runtime/morph.ts +352 -0
  281. package/src/runtime/screenshot.ts +288 -0
  282. package/src/runtime/size.ts +17 -0
  283. package/src/runtime/storage.ts +49 -0
  284. package/src/spec/actions.ts +111 -0
  285. package/src/spec/assistant-ui/thread.ts +381 -0
  286. package/src/spec/assistant-ui/toolkit.tsx +295 -0
  287. package/src/spec/assistant-ui/web.tsx +63 -0
  288. package/src/spec/assistant-ui.ts +2 -0
  289. package/src/spec/calc.ts +598 -0
  290. package/src/spec/catalog.ts +196 -0
  291. package/src/spec/expressions.ts +301 -0
  292. package/src/spec/patch.ts +112 -0
  293. package/src/spec/pending.ts +36 -0
  294. package/src/spec/pointer.ts +170 -0
  295. package/src/spec/prompt.ts +243 -0
  296. package/src/spec/render-spec.ts +144 -0
  297. package/src/spec/resolve.ts +123 -0
  298. package/src/spec/resolved.ts +53 -0
  299. package/src/spec/state.ts +193 -0
  300. package/src/spec/stream.ts +230 -0
  301. package/src/spec/tools.ts +17 -0
  302. package/src/spec/tree.ts +37 -0
  303. package/src/spec/types.ts +51 -0
  304. package/src/spec/validate.ts +429 -0
  305. package/src/spec/vue/SpecRenderer.tsx +544 -0
  306. package/src/spec/vue/__tests__/harness.ts +50 -0
  307. package/src/spec/vue/useSpecStream.ts +162 -0
  308. package/src/spec/vue.ts +13 -0
  309. package/src/spec.ts +55 -0
  310. package/src/theme.ts +423 -0
  311. package/src/tools/define.ts +65 -0
  312. package/src/tools/edits.ts +73 -0
  313. package/src/tools/registry.ts +44 -0
  314. package/src/tools/tools.ts +361 -0
  315. package/src/tools.ts +32 -0
  316. package/src/version.ts +4 -0
  317. package/src/vue/Widget.tsx +74 -0
  318. package/src/vue/sync.ts +36 -0
  319. package/src/vue/themeStore.ts +157 -0
  320. package/src/vue/useThemeTokens.ts +71 -0
  321. package/src/vue/useWidget.ts +117 -0
  322. package/src/vue.ts +8 -0
  323. package/src/widget.ts +1080 -0
package/dist/widget.js ADDED
@@ -0,0 +1,655 @@
1
+ import { DEFAULT_LIGHT_TOKENS } from "./theme.js";
2
+ import { buildBootstrapHtml, themeContext } from "./bootstrap.js";
3
+ import { GENFRAME_PROTOCOL_VERSION, INIT_MESSAGE, METHODS } from "./protocol.js";
4
+ import { RPC_ERROR, RpcError, createRpcPeer, isJsonRpcMessage } from "./rpc.js";
5
+ import { VERSION } from "./version.js";
6
+ import { SafeContentFrame } from "@ulearning/ai-safe-content-frame";
7
+ // src/widget.ts
8
+ const DEFAULT_PRODUCT = "generative-frame";
9
+ /** The Safe Content Frame salt behind a widget id's stable origin. */
10
+ const widgetStorageSalt = (id) => `genframe:v1:${id}`;
11
+ /**
12
+ * An opaque origin cannot be named as a `postMessage` target, so messages to
13
+ * the frame use `"*"`. A sandboxed frame keeps its opaque origin and window
14
+ * when it navigates, so after a navigated document loads, `origin` stops
15
+ * matching and nothing is posted to the frame. Loads are counted from the
16
+ * bootstrap's first ready message, so an extra initial `about:blank` load in
17
+ * some browser cannot cut off the bootstrap itself. A navigated document can
18
+ * still post before its own `load`; that grants it nothing the widget code
19
+ * could not already do.
20
+ */
21
+ const opaqueFrame = { async renderHtml(html, container) {
22
+ const iframe = container.ownerDocument.createElement("iframe");
23
+ iframe.setAttribute("sandbox", "allow-scripts");
24
+ iframe.style.cssText = "border:none;width:100%;height:100%";
25
+ iframe.srcdoc = html;
26
+ let ready = false;
27
+ let loads = 0;
28
+ const onMessage = (event) => {
29
+ if (event.source === iframe.contentWindow && event.data?.type === "genframe:ready") ready = true;
30
+ };
31
+ window.addEventListener("message", onMessage);
32
+ iframe.addEventListener("load", () => {
33
+ if (ready) loads++;
34
+ });
35
+ container.appendChild(iframe);
36
+ return {
37
+ iframe,
38
+ get origin() {
39
+ return loads > 1 ? "" : "null";
40
+ },
41
+ sendMessage: (data, transfer) => {
42
+ if (loads > 1) return;
43
+ iframe.contentWindow?.postMessage(data, "*", transfer);
44
+ },
45
+ fullyLoadedPromiseWithTimeout: async () => {},
46
+ dispose: () => {
47
+ window.removeEventListener("message", onMessage);
48
+ iframe.remove();
49
+ }
50
+ };
51
+ } };
52
+ const resolveFrame = (options) => {
53
+ if (options.opaqueOrigin) {
54
+ if (options.frame || options.id !== void 0 || options.unsafeShimDomain !== void 0) throw new TypeError("`opaqueOrigin` renders without Safe Content Frame, so it cannot be combined with `frame`, `id`, or `unsafeShimDomain`.");
55
+ return opaqueFrame;
56
+ }
57
+ if (options.frame && (options.id !== void 0 || options.unsafeShimDomain !== void 0)) throw new TypeError("`frame` cannot be combined with `id` or `unsafeShimDomain`; configure the `SafeContentFrame` instead, with `salt: widgetStorageSalt(id)` and `unsafeShimDomain` as needed.");
58
+ return options.frame ?? new SafeContentFrame(options.product ?? DEFAULT_PRODUCT, {
59
+ ...options.id !== void 0 ? { salt: widgetStorageSalt(options.id) } : {},
60
+ ...options.unsafeShimDomain !== void 0 ? { unsafeShimDomain: options.unsafeShimDomain } : {}
61
+ });
62
+ };
63
+ /** Lets `clearWidgetStorage` send a request no public method exposes. */
64
+ const internalRequests = /* @__PURE__ */ new WeakMap();
65
+ const DEFAULT_READY_TIMEOUT_MS = 15e3;
66
+ const REQUEST_TIMEOUT_MS = 3e4;
67
+ const SCRIPT_TAG = /<script[\s>]/i;
68
+ const REVEAL_MS = 120;
69
+ const prefersReducedMotion = () => window.matchMedia?.("(prefers-reduced-motion: reduce)").matches ?? false;
70
+ const promptText = (params) => {
71
+ const content = params.content ?? [];
72
+ return Object.keys(content).filter((key) => {
73
+ const index = Number(key);
74
+ return Number.isInteger(index) && index >= 0 && index < content.length && String(index) === key;
75
+ }).map((key) => content[Number(key)]).filter((part) => typeof part === "object" && part !== null && part.type === "text" && typeof part.text === "string").map((part) => part.text).join("\n");
76
+ };
77
+ const VALID_DISPLAY_MODES = [
78
+ "inline",
79
+ "fullscreen",
80
+ "pip"
81
+ ];
82
+ const isDisplayMode = (value) => VALID_DISPLAY_MODES.some((mode) => mode === value);
83
+ const isRecord = (value) => typeof value === "object" && value !== null && !Array.isArray(value);
84
+ /** How many characters a widget's state may take as JSON. A shared object counts once for each place it appears, as it does once serialized, so checking and saving a state stay bounded however the widget shares objects. */
85
+ const MAX_STATE_LENGTH = 1e6;
86
+ /** The length of `text` once JSON quotes and escapes it. */
87
+ const jsonStringLength = (text) => {
88
+ let length = text.length + 2;
89
+ for (let index = 0; index < text.length; index++) {
90
+ const code = text.charCodeAt(index);
91
+ if (code === 34 || code === 92) length += 1;
92
+ else if (code < 32) length += code === 8 || code === 9 || code === 10 || code === 12 || code === 13 ? 1 : 5;
93
+ else if (code >= 55296 && code <= 56319) {
94
+ const next = text.charCodeAt(index + 1);
95
+ if (next >= 56320 && next <= 57343) index++;
96
+ else length += 5;
97
+ } else if (code >= 56320 && code <= 57343) length += 5;
98
+ }
99
+ return length;
100
+ };
101
+ const isPlainJson = (state) => {
102
+ let budget = MAX_STATE_LENGTH;
103
+ const spend = (length) => (budget -= length) >= 0;
104
+ const spendText = (text) => text.length + 2 <= budget && spend(jsonStringLength(text));
105
+ const ancestors = /* @__PURE__ */ new Set();
106
+ const check = (value) => {
107
+ if (value === null || typeof value === "boolean") return spend(5);
108
+ if (typeof value === "string") return spendText(value);
109
+ if (typeof value === "number") return Number.isFinite(value) && spend(String(value).length);
110
+ if (typeof value !== "object" || ancestors.has(value) || !spend(2)) return false;
111
+ const array = Array.isArray(value);
112
+ const prototype = Object.getPrototypeOf(value);
113
+ if (array ? prototype !== Array.prototype : prototype !== Object.prototype && prototype !== null) return false;
114
+ ancestors.add(value);
115
+ const keys = Reflect.ownKeys(value);
116
+ if (array && keys.length !== value.length + 1) return false;
117
+ if (array) for (let index = 0; index < value.length; index++) {
118
+ const property = Object.getOwnPropertyDescriptor(value, String(index));
119
+ if (!property || !("value" in property) || !spend(1) || !check(property.value)) return false;
120
+ }
121
+ else for (const key of keys) {
122
+ if (typeof key !== "string") return false;
123
+ const property = Object.getOwnPropertyDescriptor(value, key);
124
+ if (!property || !("value" in property) || !property.enumerable || !spendText(key) || !spend(2) || !check(property.value)) return false;
125
+ }
126
+ ancestors.delete(value);
127
+ return true;
128
+ };
129
+ return check(state);
130
+ };
131
+ const isAppInfo = (value) => isRecord(value) && typeof value["name"] === "string" && typeof value["version"] === "string";
132
+ const getAppCapabilities = (value) => {
133
+ if (!isRecord(value)) return void 0;
134
+ const { availableDisplayModes, ...rest } = value;
135
+ return {
136
+ ...rest,
137
+ ...Array.isArray(availableDisplayModes) ? { availableDisplayModes: Object.values(availableDisplayModes).filter(isDisplayMode) } : {}
138
+ };
139
+ };
140
+ const getInitializeParams = (params) => {
141
+ if (!isRecord(params)) return {};
142
+ const appCapabilities = getAppCapabilities(params["appCapabilities"]);
143
+ return {
144
+ ...isAppInfo(params["appInfo"]) ? { appInfo: params["appInfo"] } : {},
145
+ ...appCapabilities ? { appCapabilities } : {},
146
+ ...typeof params["protocolVersion"] === "string" ? { protocolVersion: params["protocolVersion"] } : {}
147
+ };
148
+ };
149
+ const defaultOpenLink = (url) => {
150
+ window.open(url, "_blank", "noopener,noreferrer");
151
+ };
152
+ /**
153
+ * Mounts a widget frame in `container`. The frame loads a bootstrap document
154
+ * once through Safe Content Frame; code then streams in over a private
155
+ * `MessagePort`, so every chunk renders without reloading the frame.
156
+ */
157
+ function createWidget(options) {
158
+ const { container } = options;
159
+ const frame = resolveFrame(options);
160
+ const minHeight = options.minHeight ?? 0;
161
+ const maxHeight = options.maxHeight ?? Number.POSITIVE_INFINITY;
162
+ const readyTimeoutMs = options.readyTimeoutMs ?? DEFAULT_READY_TIMEOUT_MS;
163
+ let tokens = options.tokens ?? DEFAULT_LIGHT_TOKENS;
164
+ let context = {
165
+ ...themeContext(tokens),
166
+ platform: "web",
167
+ ...Number.isFinite(maxHeight) ? { containerDimensions: { maxHeight } } : {},
168
+ ...options.context
169
+ };
170
+ let code = "";
171
+ let widgetState = options.initialState ?? null;
172
+ let ended = false;
173
+ let disposed = false;
174
+ let lastToolInput;
175
+ let lastToolResult;
176
+ const listeners = /* @__PURE__ */ new Map();
177
+ const emit = (event, payload) => {
178
+ for (const listener of listeners.get(event) ?? []) try {
179
+ listener(payload);
180
+ } catch (error) {
181
+ console.error(error);
182
+ }
183
+ };
184
+ const reportError = (error) => {
185
+ options.onError?.(error);
186
+ emit("error", error);
187
+ };
188
+ const onRequest = async (method, params) => {
189
+ const p = params ?? {};
190
+ switch (method) {
191
+ case METHODS.initialize: return {
192
+ protocolVersion: typeof p["protocolVersion"] === "string" ? p["protocolVersion"] : GENFRAME_PROTOCOL_VERSION,
193
+ hostInfo: {
194
+ name: "generative-frame",
195
+ version: VERSION
196
+ },
197
+ hostCapabilities: {
198
+ openLinks: {},
199
+ ...options.onCallTool ? { serverTools: {} } : {},
200
+ ...options.onMessage || options.onPrompt ? { message: { text: {} } } : {},
201
+ ...options.onUpdateModelContext ? { updateModelContext: {
202
+ text: {},
203
+ structuredContent: {}
204
+ } } : {},
205
+ logging: {}
206
+ },
207
+ hostContext: context
208
+ };
209
+ case METHODS.message:
210
+ if (options.onMessage) return options.onMessage(p);
211
+ if (options.onPrompt) {
212
+ await options.onPrompt(promptText(p));
213
+ return {};
214
+ }
215
+ throw new RpcError(RPC_ERROR.methodNotFound, "This host does not accept messages");
216
+ case METHODS.openLink: {
217
+ const url = p["url"];
218
+ let protocol = "";
219
+ try {
220
+ protocol = typeof url === "string" ? new URL(url).protocol : "";
221
+ } catch {}
222
+ if (protocol !== "http:" && protocol !== "https:") throw new RpcError(RPC_ERROR.invalidParams, "ui/open-link requires an http(s) URL");
223
+ await (options.onOpenLink ?? defaultOpenLink)(url);
224
+ return {};
225
+ }
226
+ case METHODS.callTool: {
227
+ if (!options.onCallTool) throw new RpcError(RPC_ERROR.methodNotFound, "This host does not expose tools");
228
+ if (typeof p["name"] !== "string") throw new RpcError(RPC_ERROR.invalidParams, "tools/call requires a string 'name'");
229
+ const args = p["arguments"];
230
+ if (args !== void 0 && (typeof args !== "object" || args === null || Array.isArray(args))) throw new RpcError(RPC_ERROR.invalidParams, "tools/call 'arguments' must be an object");
231
+ return options.onCallTool({
232
+ name: p["name"],
233
+ ...args !== void 0 ? { arguments: args } : {}
234
+ });
235
+ }
236
+ case METHODS.requestDisplayMode: {
237
+ const mode = p["mode"];
238
+ if (!isDisplayMode(mode)) throw new RpcError(RPC_ERROR.invalidParams, "ui/request-display-mode requires a valid 'mode'");
239
+ if (!options.onRequestDisplayMode) return { mode: context.displayMode ?? "inline" };
240
+ return await options.onRequestDisplayMode({ mode }) ?? { mode };
241
+ }
242
+ case METHODS.copyText:
243
+ if (!options.onCopyText) throw new RpcError(RPC_ERROR.methodNotFound, "This host does not copy text");
244
+ if (typeof p["text"] !== "string") throw new RpcError(RPC_ERROR.invalidParams, "genframe/copy-text requires a string 'text'");
245
+ await options.onCopyText(p["text"]);
246
+ return {};
247
+ case METHODS.updateModelContext:
248
+ if (!options.onUpdateModelContext) throw new RpcError(RPC_ERROR.methodNotFound, "This host does not accept model context");
249
+ return options.onUpdateModelContext(params);
250
+ default: throw new RpcError(RPC_ERROR.methodNotFound, `Unknown method: ${method}`);
251
+ }
252
+ };
253
+ let activeSession;
254
+ let pendingSession;
255
+ let pendingWidgetState;
256
+ let hasPendingWidgetState = false;
257
+ let heldWidgetState;
258
+ let hasHeldWidgetState = false;
259
+ const applySize = (session, size) => {
260
+ const iframe = session.iframe();
261
+ if (iframe) {
262
+ const height = Math.min(Math.max(size.height, minHeight), maxHeight);
263
+ iframe.style.height = `${height}px`;
264
+ session.reveal();
265
+ }
266
+ if (session === activeSession) {
267
+ options.onResize?.(size);
268
+ emit("resize", size);
269
+ }
270
+ };
271
+ const createSession = (hidden) => {
272
+ const slot = container.ownerDocument.createElement("div");
273
+ slot.dataset["generativeFrame"] = "";
274
+ slot.style.cssText = hidden ? "display:block;height:0;overflow:hidden;visibility:hidden;pointer-events:none;opacity:0" : "display:block;opacity:0";
275
+ container.appendChild(slot);
276
+ let rendered;
277
+ let peer;
278
+ let initSent = false;
279
+ let initializeParams = {};
280
+ let pendingInitialized;
281
+ let sessionDisposed = false;
282
+ let revealed = false;
283
+ let resolveReady;
284
+ let rejectReady;
285
+ const ready = new Promise((resolve, reject) => {
286
+ resolveReady = resolve;
287
+ rejectReady = reject;
288
+ });
289
+ ready.catch(() => {});
290
+ const session = {
291
+ slot,
292
+ ready,
293
+ peer: () => {
294
+ if (!peer) throw new Error("Widget frame is not connected");
295
+ return peer;
296
+ },
297
+ iframe: () => rendered?.iframe,
298
+ reveal() {
299
+ if (revealed) return;
300
+ revealed = true;
301
+ if (!prefersReducedMotion()) slot.style.transition = `opacity ${REVEAL_MS}ms ease-out`;
302
+ slot.style.opacity = "1";
303
+ },
304
+ deliverInitialized() {
305
+ if (session !== activeSession || !pendingInitialized) return;
306
+ const params = pendingInitialized;
307
+ pendingInitialized = void 0;
308
+ try {
309
+ options.onInitialized?.(params);
310
+ } catch {}
311
+ },
312
+ dispose() {
313
+ if (sessionDisposed) return;
314
+ sessionDisposed = true;
315
+ clearTimeout(timer);
316
+ window.removeEventListener("message", onWindowMessage);
317
+ peer?.dispose();
318
+ rendered?.dispose();
319
+ slot.remove();
320
+ rejectReady(/* @__PURE__ */ new Error("Widget disposed"));
321
+ }
322
+ };
323
+ const fail = (message) => {
324
+ if (sessionDisposed) return;
325
+ rejectReady(new Error(message));
326
+ reportError({
327
+ kind: "error",
328
+ message
329
+ });
330
+ };
331
+ const timer = setTimeout(() => fail(`Widget frame did not connect within ${readyTimeoutMs}ms`), readyTimeoutMs);
332
+ const sendInit = () => {
333
+ if (initSent || !rendered) return;
334
+ initSent = true;
335
+ const channel = new MessageChannel();
336
+ peer = createRpcPeer(channel.port1, {
337
+ onRequest: (method, params) => {
338
+ if (method === METHODS.initialize) initializeParams = getInitializeParams(params);
339
+ return onRequest(method, params);
340
+ },
341
+ onNotification: (method, params) => {
342
+ const p = params ?? {};
343
+ switch (method) {
344
+ case METHODS.ready:
345
+ clearTimeout(timer);
346
+ resolveReady();
347
+ return;
348
+ case METHODS.initialized:
349
+ pendingInitialized = initializeParams;
350
+ session.deliverInitialized();
351
+ return;
352
+ case METHODS.sizeChanged:
353
+ if (typeof p["height"] === "number" && typeof p["width"] === "number") applySize(session, {
354
+ width: p["width"],
355
+ height: p["height"]
356
+ });
357
+ return;
358
+ case METHODS.error:
359
+ if (session === activeSession) reportError(p);
360
+ return;
361
+ case METHODS.log:
362
+ if (session === activeSession) {
363
+ options.onLog?.(p);
364
+ emit("log", p);
365
+ }
366
+ return;
367
+ case METHODS.widgetState:
368
+ if (session !== pendingSession && session !== activeSession) return;
369
+ try {
370
+ if (!isPlainJson(p["state"])) return;
371
+ } catch {
372
+ return;
373
+ }
374
+ if (session === pendingSession) {
375
+ pendingWidgetState = p["state"];
376
+ hasPendingWidgetState = true;
377
+ return;
378
+ }
379
+ if (pendingSession) {
380
+ heldWidgetState = p["state"];
381
+ hasHeldWidgetState = true;
382
+ return;
383
+ }
384
+ widgetState = p["state"];
385
+ options.onWidgetState?.(widgetState);
386
+ return;
387
+ default: return;
388
+ }
389
+ }
390
+ });
391
+ const init = {
392
+ type: INIT_MESSAGE,
393
+ context,
394
+ state: widgetState
395
+ };
396
+ rendered.sendMessage(init, [channel.port2]);
397
+ };
398
+ function onWindowMessage(event) {
399
+ if (!rendered) return;
400
+ if (event.source !== rendered.iframe.contentWindow || event.origin !== rendered.origin) return;
401
+ const data = event.data;
402
+ if (data?.type === "genframe:ready") {
403
+ sendInit();
404
+ return;
405
+ }
406
+ if (peer && isJsonRpcMessage(data)) {
407
+ const target = rendered;
408
+ peer.receive(data, (message) => target.sendMessage(message));
409
+ }
410
+ }
411
+ window.addEventListener("message", onWindowMessage);
412
+ const html = buildBootstrapHtml({
413
+ hostOrigin: window.location.origin,
414
+ tokens,
415
+ ...options.csp !== void 0 ? { csp: options.csp } : {},
416
+ ...options.animate !== void 0 ? { animate: options.animate } : {},
417
+ ...options.css !== void 0 ? { css: options.css } : {}
418
+ });
419
+ frame.renderHtml(html, slot).then((result) => {
420
+ if (sessionDisposed) {
421
+ result.dispose();
422
+ return;
423
+ }
424
+ rendered = result;
425
+ const style = result.iframe.style;
426
+ style.display = "block";
427
+ style.height = `${minHeight}px`;
428
+ style.colorScheme = tokens.colorScheme;
429
+ result.iframe.setAttribute("title", "Generated widget");
430
+ result.fullyLoadedPromiseWithTimeout(readyTimeoutMs).catch((error) => fail(error instanceof Error ? error.message : String(error)));
431
+ }, (error) => fail(error instanceof Error ? error.message : String(error)));
432
+ return session;
433
+ };
434
+ activeSession = createSession(false);
435
+ let queue = [];
436
+ activeSession.ready.then(() => {
437
+ const ops = queue ?? [];
438
+ queue = void 0;
439
+ emit("ready", void 0);
440
+ for (const op of ops) if (op.kind === "write") activeSession?.peer().notify(METHODS.write, { chunk: op.chunk });
441
+ else op.run();
442
+ }, () => {});
443
+ const enqueue = (run) => {
444
+ if (queue) queue.push({
445
+ kind: "run",
446
+ run
447
+ });
448
+ else run();
449
+ };
450
+ const request = (method, params) => new Promise((resolve, reject) => {
451
+ const session = activeSession;
452
+ if (!session || disposed) {
453
+ reject(/* @__PURE__ */ new Error("Widget disposed"));
454
+ return;
455
+ }
456
+ session.ready.then(() => enqueue(() => {
457
+ session.peer().request(method, params, { timeoutMs: REQUEST_TIMEOUT_MS }).then((value) => resolve(value), reject);
458
+ }), reject);
459
+ });
460
+ const notify = (method, params) => {
461
+ const session = activeSession;
462
+ if (!session || disposed) return;
463
+ enqueue(() => {
464
+ try {
465
+ session.peer().notify(method, params);
466
+ } catch {}
467
+ });
468
+ };
469
+ const replayState = (session) => {
470
+ const peer = session.peer();
471
+ if (lastToolInput) peer.notify(lastToolInput.partial ? METHODS.toolInputPartial : METHODS.toolInput, { arguments: lastToolInput.args });
472
+ if (lastToolResult) peer.notify(METHODS.toolResult, lastToolResult.result);
473
+ };
474
+ const remount = async (next) => {
475
+ const previous = activeSession;
476
+ pendingSession?.dispose();
477
+ const session = createSession(true);
478
+ pendingSession = session;
479
+ hasPendingWidgetState = false;
480
+ let result;
481
+ try {
482
+ await session.ready;
483
+ if (disposed || pendingSession !== session) throw new Error("Widget replacement superseded");
484
+ replayState(session);
485
+ result = await session.peer().request(METHODS.replace, { code: next }, { timeoutMs: REQUEST_TIMEOUT_MS });
486
+ if (disposed || pendingSession !== session) throw new Error("Widget replacement superseded");
487
+ } catch (error) {
488
+ const superseded = !disposed && pendingSession !== session;
489
+ if (pendingSession === session) {
490
+ pendingSession = void 0;
491
+ hasPendingWidgetState = false;
492
+ if (hasHeldWidgetState && !disposed) {
493
+ hasHeldWidgetState = false;
494
+ widgetState = heldWidgetState;
495
+ try {
496
+ options.onWidgetState?.(widgetState);
497
+ } catch {}
498
+ }
499
+ }
500
+ session.dispose();
501
+ if (disposed) throw new Error("Widget disposed");
502
+ throw superseded ? /* @__PURE__ */ new Error("Widget replacement superseded") : error;
503
+ }
504
+ pendingSession = void 0;
505
+ hasHeldWidgetState = false;
506
+ activeSession = session;
507
+ if (hasPendingWidgetState) widgetState = pendingWidgetState;
508
+ session.slot.style.cssText = "display:block";
509
+ previous?.dispose();
510
+ if (hasPendingWidgetState) {
511
+ hasPendingWidgetState = false;
512
+ try {
513
+ options.onWidgetState?.(widgetState);
514
+ } catch {}
515
+ }
516
+ session.deliverInitialized();
517
+ applySize(session, result.size);
518
+ return result;
519
+ };
520
+ const handle = {
521
+ ready: activeSession.ready,
522
+ get code() {
523
+ return code;
524
+ },
525
+ get ended() {
526
+ return ended;
527
+ },
528
+ get iframe() {
529
+ return activeSession?.iframe();
530
+ },
531
+ write(chunk) {
532
+ if (disposed || !chunk) return;
533
+ if (ended) throw new Error("Cannot write after end(); use replace()");
534
+ code += chunk;
535
+ const last = queue?.[queue.length - 1];
536
+ if (queue) {
537
+ if (last?.kind === "write") last.chunk += chunk;
538
+ else queue.push({
539
+ kind: "write",
540
+ chunk
541
+ });
542
+ return;
543
+ }
544
+ activeSession?.peer().notify(METHODS.write, { chunk });
545
+ },
546
+ async end() {
547
+ ended = true;
548
+ const result = await request(METHODS.end);
549
+ emit("end", result);
550
+ return result;
551
+ },
552
+ async replace(next) {
553
+ const scriptsRan = pendingSession !== void 0 || ended && SCRIPT_TAG.test(code);
554
+ code = next;
555
+ ended = true;
556
+ const result = scriptsRan ? await remount(next) : await request(METHODS.replace, { code: next });
557
+ emit("end", result);
558
+ return result;
559
+ },
560
+ setTheme(next) {
561
+ tokens = next;
562
+ context = {
563
+ ...context,
564
+ ...themeContext(next)
565
+ };
566
+ notify(METHODS.hostContextChanged, themeContext(next));
567
+ const session = activeSession;
568
+ const followTheme = () => {
569
+ const iframe = session?.iframe();
570
+ if (iframe && tokens === next) iframe.style.colorScheme = next.colorScheme;
571
+ };
572
+ request(METHODS.inspect).then(followTheme, followTheme);
573
+ },
574
+ setContext(partial) {
575
+ context = {
576
+ ...context,
577
+ ...partial
578
+ };
579
+ notify(METHODS.hostContextChanged, partial);
580
+ },
581
+ notifyToolInput(args, notifyOptions) {
582
+ const partial = notifyOptions?.partial ?? false;
583
+ lastToolInput = {
584
+ args,
585
+ partial
586
+ };
587
+ notify(partial ? METHODS.toolInputPartial : METHODS.toolInput, { arguments: args });
588
+ },
589
+ notifyToolResult(result) {
590
+ lastToolResult = { result };
591
+ notify(METHODS.toolResult, result);
592
+ },
593
+ screenshot(screenshotOptions) {
594
+ return request(METHODS.screenshot, screenshotOptions ?? {});
595
+ },
596
+ async inspect() {
597
+ return {
598
+ ...await request(METHODS.inspect),
599
+ code
600
+ };
601
+ },
602
+ on(event, listener) {
603
+ let set = listeners.get(event);
604
+ if (!set) {
605
+ set = /* @__PURE__ */ new Set();
606
+ listeners.set(event, set);
607
+ }
608
+ set.add(listener);
609
+ return () => set.delete(listener);
610
+ },
611
+ dispose() {
612
+ if (disposed) return;
613
+ disposed = true;
614
+ queue = void 0;
615
+ pendingSession?.dispose();
616
+ pendingSession = void 0;
617
+ activeSession?.dispose();
618
+ activeSession = void 0;
619
+ listeners.clear();
620
+ }
621
+ };
622
+ internalRequests.set(handle, request);
623
+ if (options.code !== void 0) {
624
+ handle.write(options.code);
625
+ handle.end().catch(() => {});
626
+ }
627
+ return handle;
628
+ }
629
+ /**
630
+ * Clears what widgets with this `id` stored (localStorage, sessionStorage,
631
+ * IndexedDB, Cache Storage, and cookies) by loading a hidden frame on the
632
+ * same origin. Resolves with the storage kinds that were cleared.
633
+ */
634
+ async function clearWidgetStorage(id, options = {}) {
635
+ const container = document.createElement("div");
636
+ container.setAttribute("aria-hidden", "true");
637
+ container.style.cssText = "position:fixed;left:0;top:0;width:1px;height:1px;overflow:hidden;opacity:0;pointer-events:none;";
638
+ document.body.appendChild(container);
639
+ let widget;
640
+ try {
641
+ widget = createWidget({
642
+ ...options,
643
+ container,
644
+ id
645
+ });
646
+ await widget.ready;
647
+ return await internalRequests.get(widget)(METHODS.clearStorage);
648
+ } finally {
649
+ widget?.dispose();
650
+ container.remove();
651
+ }
652
+ }
653
+ export { clearWidgetStorage, createWidget, widgetStorageSalt };
654
+
655
+ //# sourceMappingURL=widget.js.map