@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
@@ -0,0 +1,747 @@
1
+ import {
2
+ useAssistantInstructions,
3
+ useAui,
4
+ useAuiState,
5
+ useToolArgsStatus,
6
+ type Toolkit,
7
+ type ToolCallMessagePartProps,
8
+ } from "@ulearning/ai-sdk";
9
+ import {
10
+ computed,
11
+ defineComponent,
12
+ h,
13
+ onDeactivated,
14
+ onScopeDispose,
15
+ shallowRef,
16
+ toValue,
17
+ watch,
18
+ watchEffect,
19
+ type Component,
20
+ type MaybeRefOrGetter,
21
+ } from "vue";
22
+ import { useIsomorphicLayoutEffect } from "@ulearning/ai-store/internal";
23
+ import { previewWidget, type PreviewOptions } from "../preview";
24
+ import type { ConsoleEntry, WidgetError } from "../protocol";
25
+ import { buildRepairFeedback } from "../repair/repair";
26
+ import { useThemeTokens } from "../vue/useThemeTokens";
27
+ import { syncWidgetCode } from "../vue/sync";
28
+ import { useWidget, type UseWidgetOptions } from "../vue/useWidget";
29
+ import type { ThemeTokenSources } from "../theme";
30
+ import { createWidgetRegistry, type WidgetRegistry } from "../tools/registry";
31
+ import {
32
+ buildWidgetInstructions,
33
+ createWidgetTools,
34
+ type CreateWidgetToolsOptions,
35
+ type EditWidgetInput,
36
+ type PreviewWidgetInput,
37
+ type ShowWidgetInput,
38
+ type WidgetInstructionsOptions,
39
+ type WidgetTools,
40
+ } from "../tools/tools";
41
+ import type { AnyTool, ToolDefinition } from "../tools/define";
42
+ import {
43
+ hasLaterToolCallInMessage,
44
+ type MessageLike,
45
+ type ToolCall,
46
+ } from "./tool-calls";
47
+ import { toolkitEntry } from "./entry";
48
+ import type { ToolkitDisplay, WidgetToolkitExtension } from "./extension";
49
+ import { resolveWidgetCode, resolveWidgetOrigin } from "./history";
50
+ import {
51
+ frameContext,
52
+ savedContext,
53
+ type ModelContextSource,
54
+ type SharedContext,
55
+ } from "./modelContext";
56
+ import { createSnapshotSaver } from "./snapshotSaver";
57
+ import { useSharedContext } from "./useSharedContext";
58
+
59
+ /**
60
+ * Where theme tokens come from in an assistant-ui app: shadcn/ui variables
61
+ * first, because Tailwind v4 aliases `--color-accent` to shadcn's muted
62
+ * `--accent` rather than the primary color.
63
+ */
64
+ export const ASSISTANT_UI_TOKEN_SOURCES: ThemeTokenSources = {
65
+ "--color-background": ["--background"],
66
+ "--color-surface": ["--card", "--background"],
67
+ "--color-surface-muted": ["--muted", "--secondary"],
68
+ "--color-text": ["--foreground"],
69
+ "--color-text-muted": ["--muted-foreground"],
70
+ "--color-text-subtle": ["--muted-foreground"],
71
+ "--color-border": ["--border"],
72
+ "--color-border-strong": ["--input", "--border"],
73
+ "--color-accent": ["--primary"],
74
+ "--color-accent-text": ["--primary-foreground"],
75
+ "--color-danger": ["--destructive"],
76
+ "--color-success": ["--aui-success", "--color-success"],
77
+ "--color-warning": ["--aui-warning", "--color-warning"],
78
+ "--color-info": ["--color-info"],
79
+ };
80
+
81
+ /** The app's assistant-ui (shadcn/ui) theme as widget tokens, kept in sync. */
82
+ export function useAssistantUiThemeTokens(element?: Element | null) {
83
+ return useThemeTokens(element, ASSISTANT_UI_TOKEN_SOURCES);
84
+ }
85
+
86
+ export type WidgetToolkitOptions = Omit<
87
+ CreateWidgetToolsOptions,
88
+ "registry"
89
+ > & {
90
+ registry?: WidgetRegistry;
91
+ /**
92
+ * `frontend` (default): the tools execute in the browser and the server
93
+ * forwards their schemas (`frontendTools` from `@ulearning/ai-sdk-adapter`).
94
+ * `backend`: the server executes `createWidgetTools()` and the toolkit
95
+ * only renders.
96
+ */
97
+ execution?: "frontend" | "backend";
98
+ /** Options for every widget frame (csp, product, maxHeight, handlers, …). */
99
+ widget?: Omit<UseWidgetOptions, "tokens" | "initialState">;
100
+ /** Spec mode: `createSpecToolkit(catalog, { components })` from `generative-frame/spec/assistant-ui`. */
101
+ spec?: WidgetToolkitExtension;
102
+ /** More tools for the model, such as data lookups. They execute like the widget tools and render nothing in the thread. */
103
+ extraTools?: Record<string, AnyTool>;
104
+ /** Where the theme is read from. Defaults to the document root. */
105
+ themeElement?: Element | null;
106
+ /** How the widget tools are presented relative to the reasoning trace. Defaults to `standalone`. */
107
+ display?: ToolkitDisplay;
108
+ /**
109
+ * With frontend execution, `show_widget` and `edit_widget` wait for the
110
+ * frame to finish rendering and return its errors as `render`. `false`
111
+ * returns as soon as the code is stored.
112
+ */
113
+ renderReport?: RenderReportOptions | false;
114
+ /** Keep the PNG in `preview_widget` results. Defaults to false. */
115
+ previewScreenshot?: boolean;
116
+ /**
117
+ * The storage id of a widget's frame, from the thread's persisted id and
118
+ * the `toolCallId` of the `show_widget` call that created it, so a widget
119
+ * keeps its localStorage across reloads and edits. Defaults to
120
+ * `<remoteId>:<toolCallId>`, or `aui:<toolCallId>` while the thread has no
121
+ * remote id; `false` gives every frame a fresh origin. Derive it from host
122
+ * data only, never from tool arguments the model wrote.
123
+ */
124
+ widgetId?:
125
+ | false
126
+ | ((call: { toolCallId: string; threadId: string | undefined }) => string);
127
+ };
128
+
129
+ export type RenderReportOptions = {
130
+ /** How long a tool result waits for the frame. Defaults to 10000 ms. */
131
+ timeoutMs?: number;
132
+ /** Wait after the code ends so late script errors are caught. Defaults to 300 ms. */
133
+ settleMs?: number;
134
+ };
135
+
136
+ /** How a widget rendered in the thread, attached to tool results as `render`. */
137
+ export type WidgetRenderReport =
138
+ | {
139
+ status: "rendered";
140
+ ok: boolean;
141
+ blank: boolean;
142
+ height: number;
143
+ errors: WidgetError[];
144
+ /** Console warnings and errors, most recent last. */
145
+ console: ConsoleEntry[];
146
+ feedback: string;
147
+ }
148
+ | { status: "timeout"; feedback: string };
149
+
150
+ export type WidgetToolkit = {
151
+ /** Pass to `Tools({ toolkit })`, or spread into your own toolkit. */
152
+ toolkit: Toolkit;
153
+ /** The tool definitions behind the toolkit, for instructions or the server. */
154
+ tools: WidgetTools & Record<string, AnyTool>;
155
+ /** Latest code per widget title. */
156
+ registry: WidgetRegistry;
157
+ };
158
+
159
+ type ShowArgs = Partial<ShowWidgetInput>;
160
+ type EditArgs = Partial<EditWidgetInput>;
161
+
162
+ const REPORT_BACKLOG = 50;
163
+ /** A later show takes over once it has code to show; a later edit only once its arguments are settled and its edits apply, so the widget stays while a show has no code or an edit streams or fails. */
164
+ const takesOverWidget = (messages: readonly MessageLike[], call: ToolCall) =>
165
+ (call.name === "show_widget" &&
166
+ typeof call.args["widget_code"] === "string" &&
167
+ call.args["widget_code"].trim() !== "") ||
168
+ (call.name === "edit_widget" &&
169
+ call.settled &&
170
+ resolveWidgetCode(messages, call.id) !== undefined);
171
+
172
+ /** Hands render reports from the thread's widgets to the tool calls waiting for them. */
173
+ function createRenderReports() {
174
+ const early = new Map<string, WidgetRenderReport>();
175
+ const waiters = new Map<string, (report: WidgetRenderReport) => void>();
176
+ return {
177
+ report(toolCallId: string, report: WidgetRenderReport) {
178
+ const waiter = waiters.get(toolCallId);
179
+ if (waiter) {
180
+ waiters.delete(toolCallId);
181
+ waiter(report);
182
+ return;
183
+ }
184
+ early.set(toolCallId, report);
185
+ // Calls rendered from history never execute, so their reports are dropped.
186
+ if (early.size > REPORT_BACKLOG) {
187
+ early.delete(early.keys().next().value as string);
188
+ }
189
+ },
190
+ wait(
191
+ toolCallId: string,
192
+ timeoutMs: number,
193
+ signal?: AbortSignal,
194
+ ): Promise<WidgetRenderReport> {
195
+ const ready = early.get(toolCallId);
196
+ if (ready) {
197
+ early.delete(toolCallId);
198
+ return Promise.resolve(ready);
199
+ }
200
+ return new Promise((resolve) => {
201
+ const finish = (report: WidgetRenderReport) => {
202
+ clearTimeout(timer);
203
+ signal?.removeEventListener("abort", onAbort);
204
+ waiters.delete(toolCallId);
205
+ resolve(report);
206
+ };
207
+ const timedOut = () =>
208
+ finish({
209
+ status: "timeout",
210
+ feedback: `The widget had not finished rendering after ${timeoutMs} ms, so no render errors are known.`,
211
+ });
212
+ const onAbort = timedOut;
213
+ const timer = setTimeout(timedOut, timeoutMs);
214
+ signal?.addEventListener("abort", onAbort, { once: true });
215
+ waiters.set(toolCallId, finish);
216
+ });
217
+ },
218
+ };
219
+ }
220
+
221
+ const visuallyHidden = {
222
+ position: "absolute",
223
+ width: 1,
224
+ height: 1,
225
+ overflow: "hidden",
226
+ clip: "rect(0 0 0 0)",
227
+ whiteSpace: "nowrap",
228
+ } as const;
229
+
230
+ const previewFrameOptions = (
231
+ widget: WidgetToolkitOptions["widget"],
232
+ ): PreviewOptions => ({
233
+ ...(widget?.product !== undefined ? { product: widget.product } : {}),
234
+ ...(widget?.frame !== undefined ? { frame: widget.frame } : {}),
235
+ ...(widget?.unsafeShimDomain !== undefined
236
+ ? { unsafeShimDomain: widget.unsafeShimDomain }
237
+ : {}),
238
+ ...(widget?.opaqueOrigin !== undefined
239
+ ? { opaqueOrigin: widget.opaqueOrigin }
240
+ : {}),
241
+ });
242
+
243
+ function savedWidgetState(
244
+ log: ToolCallMessagePartProps<ShowArgs>["unstable_interactions"],
245
+ toolCallId: string,
246
+ ): unknown {
247
+ const entries = log?.entries ?? [];
248
+ for (let index = entries.length - 1; index >= 0; index--) {
249
+ const { type, payload } = entries[index]!;
250
+ if (
251
+ type === "action" &&
252
+ payload["type"] === "widgetState" &&
253
+ payload["toolCallId"] === toolCallId
254
+ ) {
255
+ return payload["state"] ?? null;
256
+ }
257
+ }
258
+ return null;
259
+ }
260
+
261
+ function useWidgetModelContext(source: ModelContextSource) {
262
+ const aui = useAui();
263
+ useIsomorphicLayoutEffect(
264
+ () =>
265
+ aui.modelContext.register({
266
+ getModelContext: () => {
267
+ const system = source.read();
268
+ return system ? { system } : {};
269
+ },
270
+ subscribe: source.subscribe,
271
+ }),
272
+ [],
273
+ );
274
+ }
275
+
276
+ /**
277
+ * Renders `show_widget`, `edit_widget`, and `render_spec` tool calls in an
278
+ * assistant-ui thread: widget code streams into a frame from the partial
279
+ * tool arguments, `sendPrompt` adds its text to the thread composer, and the
280
+ * frame follows the app's theme. With `execution: "frontend"` the tools run
281
+ * in the browser too, so the server only forwards their schemas.
282
+ */
283
+ export function createWidgetToolkit(
284
+ options: WidgetToolkitOptions = {},
285
+ ): WidgetToolkit {
286
+ const registry = options.registry ?? createWidgetRegistry();
287
+ const tools: WidgetTools & Record<string, AnyTool> = createWidgetTools({
288
+ ...options,
289
+ guidance: {
290
+ ...options.guidance,
291
+ hostApi: {
292
+ modelContext: true,
293
+ clipboard: options.widget?.onCopyText !== undefined,
294
+ ...options.guidance?.hostApi,
295
+ },
296
+ },
297
+ preview:
298
+ options.preview ??
299
+ ((code, previewOptions) =>
300
+ previewWidget(code, {
301
+ ...previewFrameOptions(options.widget),
302
+ ...previewOptions,
303
+ })),
304
+ registry,
305
+ modules: [...(options.modules ?? []), ...(options.spec?.modules ?? [])],
306
+ extraTools: { ...options.extraTools, ...options.spec?.tools },
307
+ });
308
+ const display = options.display ?? "standalone";
309
+ const execution = options.execution ?? "frontend";
310
+ const reports = createRenderReports();
311
+ const reportOptions =
312
+ options.renderReport === false ? undefined : (options.renderReport ?? {});
313
+ const settleMs = reportOptions?.settleMs ?? 300;
314
+ const timeoutMs = reportOptions?.timeoutMs ?? 10_000;
315
+
316
+ function useWidgetProps() {
317
+ const aui = useAui();
318
+ const tokens = useAssistantUiThemeTokens(options.themeElement);
319
+ return computed(() => ({
320
+ ...options.widget,
321
+ tokens: tokens.value,
322
+ onPrompt:
323
+ options.widget?.onPrompt ??
324
+ ((text: string) => {
325
+ const composer = aui.thread.composer();
326
+ composer.setText(
327
+ [composer.getState().text, text]
328
+ .filter((part) => part.trim())
329
+ .join(" "),
330
+ );
331
+ }),
332
+ }));
333
+ }
334
+
335
+ function useStorageId(origin: MaybeRefOrGetter<string | undefined>) {
336
+ const threadId = useAuiState((state) => state.threadListItem?.remoteId);
337
+ return computed(() => {
338
+ const toolCallId = toValue(origin);
339
+ if (options.widgetId === false || !toolCallId) return undefined;
340
+ if (options.widgetId)
341
+ return options.widgetId({ toolCallId, threadId: threadId.value });
342
+ return `${threadId.value ?? "aui"}:${toolCallId}`;
343
+ });
344
+ }
345
+
346
+ type ToolWidgetProps = {
347
+ code: string;
348
+ streaming: boolean;
349
+ title: string;
350
+ toolCallId: string;
351
+ storageId: string | undefined;
352
+ unstable_interactions: ToolCallMessagePartProps<ShowArgs>["unstable_interactions"];
353
+ unstable_recordInteraction: ToolCallMessagePartProps<ShowArgs>["unstable_recordInteraction"];
354
+ };
355
+ const ToolWidget = defineComponent(
356
+ (props: ToolWidgetProps) => {
357
+ const widgetProps = useWidgetProps();
358
+ const initialState = savedWidgetState(
359
+ props.unstable_interactions,
360
+ props.toolCallId,
361
+ );
362
+ const initialContext = savedContext(
363
+ props.unstable_interactions,
364
+ props.toolCallId,
365
+ "widgetState",
366
+ );
367
+ const latest = { state: initialState, context: initialContext };
368
+ const snapshot = () =>
369
+ latest.context
370
+ ? { state: latest.state, context: latest.context }
371
+ : latest.state === null
372
+ ? null
373
+ : { state: latest.state };
374
+ let disposed = false;
375
+ const saver = createSnapshotSaver<{
376
+ state: unknown;
377
+ context?: SharedContext;
378
+ } | null>(snapshot(), (saved) =>
379
+ props.unstable_recordInteraction?.({
380
+ type: "action",
381
+ payload: {
382
+ type: "widgetState",
383
+ toolCallId: props.toolCallId,
384
+ state: saved ? saved.state : null,
385
+ ...(saved?.context ? { context: saved.context } : {}),
386
+ },
387
+ }),
388
+ );
389
+ onDeactivated(() => saver.flush());
390
+ onScopeDispose(() => {
391
+ disposed = true;
392
+ saver.flush();
393
+ });
394
+ const share = useSharedContext(() => ({
395
+ title: props.title,
396
+ toolCallId: props.toolCallId,
397
+ initial: widgetProps.value.onUpdateModelContext
398
+ ? undefined
399
+ : initialContext,
400
+ useModelContext: useWidgetModelContext,
401
+ onShare(context) {
402
+ latest.context = context;
403
+ saver.schedule(snapshot);
404
+ },
405
+ }));
406
+ const { ref, widget } = useWidget(() => {
407
+ const current = widgetProps.value;
408
+ return {
409
+ ...current,
410
+ initialState,
411
+ ...(props.storageId !== undefined &&
412
+ !current.frame &&
413
+ !current.opaqueOrigin
414
+ ? { id: props.storageId }
415
+ : {}),
416
+ onWidgetState(state: unknown) {
417
+ if (disposed) return;
418
+ latest.state = state;
419
+ saver.schedule(snapshot);
420
+ current.onWidgetState?.(state);
421
+ },
422
+ onUpdateModelContext:
423
+ current.onUpdateModelContext ??
424
+ ((params: unknown) => {
425
+ share(frameContext(params));
426
+ return {};
427
+ }),
428
+ };
429
+ });
430
+ watch(
431
+ [widget, () => props.toolCallId],
432
+ ([handle, toolCallId], _previous, cleanup) => {
433
+ if (!handle || !reportOptions) return;
434
+ let valid = true;
435
+ let timer: ReturnType<typeof setTimeout> | undefined;
436
+ const off = handle.on("end", () => {
437
+ off();
438
+ timer = setTimeout(() => {
439
+ void handle
440
+ .inspect()
441
+ .then((inspection) => {
442
+ if (!valid || disposed || widget.value !== handle) return;
443
+ const feedback = buildRepairFeedback({
444
+ errors: inspection.errors,
445
+ console: inspection.console,
446
+ blank: inspection.blank,
447
+ height: inspection.size.height,
448
+ });
449
+ reports.report(toolCallId, {
450
+ status: "rendered",
451
+ ok: feedback.ok,
452
+ blank: feedback.blank,
453
+ height: inspection.size.height,
454
+ errors: feedback.errors,
455
+ console: feedback.console,
456
+ feedback: feedback.text,
457
+ });
458
+ })
459
+ .catch(() => {});
460
+ }, settleMs);
461
+ });
462
+ cleanup(() => {
463
+ valid = false;
464
+ off();
465
+ clearTimeout(timer);
466
+ });
467
+ },
468
+ { immediate: true, flush: "sync" },
469
+ );
470
+ watchEffect(
471
+ () => {
472
+ if (widget.value)
473
+ syncWidgetCode(widget.value, props.code, props.streaming);
474
+ },
475
+ { flush: "post" },
476
+ );
477
+ return () => h("div", { ref });
478
+ },
479
+ {
480
+ name: "ToolWidget",
481
+ props: [
482
+ "code",
483
+ "streaming",
484
+ "title",
485
+ "toolCallId",
486
+ "storageId",
487
+ "unstable_interactions",
488
+ "unstable_recordInteraction",
489
+ ],
490
+ },
491
+ );
492
+
493
+ function useWidgetCallSuperseded(
494
+ messages: MaybeRefOrGetter<readonly MessageLike[]>,
495
+ toolCallId: () => string,
496
+ title: () => string,
497
+ ) {
498
+ const superseded = computed(() => {
499
+ const current = toValue(messages);
500
+ return hasLaterToolCallInMessage(current, toolCallId(), title(), (call) =>
501
+ takesOverWidget(current, call),
502
+ );
503
+ });
504
+ watch(
505
+ [superseded, toolCallId],
506
+ ([replaced, id]) => {
507
+ if (replaced && reportOptions)
508
+ reports.report(id, {
509
+ status: "timeout",
510
+ feedback:
511
+ "This widget was superseded by a later call with the same title in this message and was not rendered.",
512
+ });
513
+ },
514
+ { immediate: true, flush: "sync" },
515
+ );
516
+ return superseded;
517
+ }
518
+ const partProps: (
519
+ | "args"
520
+ | "toolCallId"
521
+ | "unstable_interactions"
522
+ | "unstable_recordInteraction"
523
+ )[] = [
524
+ "args",
525
+ "toolCallId",
526
+ "unstable_interactions",
527
+ "unstable_recordInteraction",
528
+ ];
529
+ const ShowWidgetUI = defineComponent(
530
+ (props: ToolCallMessagePartProps<ShowArgs>) => {
531
+ const status = useToolArgsStatus<ShowWidgetInput>();
532
+ const messages = useAuiState((state) => state.thread.messages);
533
+ const streaming = computed(
534
+ () => status.value.propStatus.widget_code !== "complete",
535
+ );
536
+ const code = computed(() =>
537
+ typeof props.args.widget_code === "string"
538
+ ? props.args.widget_code
539
+ : "",
540
+ );
541
+ const title = computed(() =>
542
+ typeof props.args.title === "string" ? props.args.title : "",
543
+ );
544
+ const storageId = useStorageId(() => props.toolCallId);
545
+ const superseded = useWidgetCallSuperseded(
546
+ messages,
547
+ () => props.toolCallId,
548
+ () => title.value,
549
+ );
550
+ watch(
551
+ [streaming, title, code],
552
+ ([running, name, source]) => {
553
+ if (!running && name && source && registry.get(name)?.code !== source)
554
+ registry.set(name, source);
555
+ },
556
+ { immediate: true, flush: "sync" },
557
+ );
558
+ return () => {
559
+ if (superseded.value) return null;
560
+ const loading = props.args.loading_messages?.[0];
561
+ return h("div", { "data-gf-widget": title.value || undefined }, [
562
+ ...(streaming.value && !code.value && loading
563
+ ? [
564
+ h(
565
+ "p",
566
+ { role: "status", style: { margin: "4px 0", opacity: 0.7 } },
567
+ loading,
568
+ ),
569
+ ]
570
+ : []),
571
+ h(ToolWidget, {
572
+ code: code.value,
573
+ streaming: streaming.value,
574
+ title: title.value,
575
+ toolCallId: props.toolCallId,
576
+ storageId: storageId.value,
577
+ unstable_interactions: props.unstable_interactions,
578
+ unstable_recordInteraction: props.unstable_recordInteraction,
579
+ }),
580
+ ]);
581
+ };
582
+ },
583
+ { name: "ShowWidgetUI", inheritAttrs: false, props: partProps },
584
+ );
585
+
586
+ const EditWidgetUI = defineComponent(
587
+ (props: ToolCallMessagePartProps<EditArgs>) => {
588
+ const status = useToolArgsStatus<EditWidgetInput>();
589
+ const messages = useAuiState((state) => state.thread.messages);
590
+ const title = computed(() =>
591
+ typeof props.args.title === "string" ? props.args.title : "",
592
+ );
593
+ const complete = computed(() => status.value.argsStatus === "complete");
594
+ const superseded = useWidgetCallSuperseded(
595
+ messages,
596
+ () => props.toolCallId,
597
+ () => title.value,
598
+ );
599
+ const code = computed(() =>
600
+ complete.value
601
+ ? resolveWidgetCode(messages.value, props.toolCallId)
602
+ : undefined,
603
+ );
604
+ const origin = computed(() =>
605
+ complete.value
606
+ ? resolveWidgetOrigin(messages.value, props.toolCallId)
607
+ : undefined,
608
+ );
609
+ const storageId = useStorageId(origin);
610
+ watch(
611
+ [title, code],
612
+ ([name, source]) => {
613
+ if (
614
+ name &&
615
+ source !== undefined &&
616
+ registry.get(name)?.code !== source
617
+ )
618
+ registry.set(name, source);
619
+ },
620
+ { immediate: true, flush: "sync" },
621
+ );
622
+ return () => {
623
+ if (superseded.value) return null;
624
+ if (!complete.value)
625
+ return h(
626
+ "p",
627
+ { role: "status", style: { margin: "4px 0", opacity: 0.7 } },
628
+ `Updating ${title.value || "widget"}…`,
629
+ );
630
+ if (code.value === undefined)
631
+ return h(
632
+ "p",
633
+ { role: "note", style: { margin: "4px 0", opacity: 0.7 } },
634
+ `${title.value || "The widget"} could not be updated.`,
635
+ );
636
+ return h("div", { "data-gf-widget": title.value || undefined }, [
637
+ h("span", { style: visuallyHidden }, `Updated ${title.value}`),
638
+ h(ToolWidget, {
639
+ code: code.value,
640
+ streaming: false,
641
+ title: title.value,
642
+ toolCallId: props.toolCallId,
643
+ storageId: storageId.value,
644
+ unstable_interactions: props.unstable_interactions,
645
+ unstable_recordInteraction: props.unstable_recordInteraction,
646
+ }),
647
+ ]);
648
+ };
649
+ },
650
+ { name: "EditWidgetUI", inheritAttrs: false, props: partProps },
651
+ );
652
+ const Silent = () => null;
653
+
654
+ type ExecuteContext = { toolCallId: string; abortSignal?: AbortSignal };
655
+
656
+ const entry = <I, O>(
657
+ tool: ToolDefinition<I, O>,
658
+ render: Component,
659
+ entryDisplay: ToolkitDisplay,
660
+ execute?: (input: I, context: ExecuteContext) => Promise<unknown>,
661
+ ) =>
662
+ toolkitEntry(tool, render, { execution, display: entryDisplay }, execute);
663
+
664
+ const withRenderReport =
665
+ <I,>(tool: ToolDefinition<I, { ok: boolean }>) =>
666
+ async (input: I, context: ExecuteContext) => {
667
+ const result = await tool.execute(input);
668
+ if (!result.ok || !reportOptions) return result;
669
+ const render = await reports.wait(
670
+ context.toolCallId,
671
+ timeoutMs,
672
+ context.abortSignal,
673
+ );
674
+ return { ...result, render };
675
+ };
676
+
677
+ const preview = async (input: PreviewWidgetInput) => {
678
+ const { screenshot, ...result } = await tools.preview_widget.execute(input);
679
+ const feedback = buildRepairFeedback(result).text;
680
+ return options.previewScreenshot && screenshot !== undefined
681
+ ? { ...result, screenshot, feedback }
682
+ : { ...result, feedback };
683
+ };
684
+
685
+ const toolkit: Toolkit = {
686
+ read_me: entry(tools.read_me, Silent, "inline"),
687
+ show_widget: entry(
688
+ tools.show_widget,
689
+ ShowWidgetUI,
690
+ display,
691
+ withRenderReport(tools.show_widget),
692
+ ),
693
+ edit_widget: entry(
694
+ tools.edit_widget,
695
+ EditWidgetUI,
696
+ display,
697
+ withRenderReport(tools.edit_widget),
698
+ ),
699
+ preview_widget: entry(tools.preview_widget, Silent, "inline", preview),
700
+ ...Object.fromEntries(
701
+ Object.entries(options.extraTools ?? {}).map(([name, tool]) => [
702
+ name,
703
+ entry(tool, Silent, "inline"),
704
+ ]),
705
+ ),
706
+ ...options.spec?.entries({ execution, display }),
707
+ };
708
+
709
+ return { toolkit, tools, registry };
710
+ }
711
+
712
+ /** Adds the widget guidance to the model while this Vue scope is active. */
713
+ export function useWidgetInstructions(
714
+ tools: MaybeRefOrGetter<WidgetTools & Record<string, AnyTool>>,
715
+ options: MaybeRefOrGetter<WidgetInstructionsOptions> = {},
716
+ ): void {
717
+ const instruction = shallowRef("");
718
+ useIsomorphicLayoutEffect(
719
+ () =>
720
+ watch(
721
+ [
722
+ () => toValue(tools),
723
+ () => JSON.stringify(toValue(options).preload ?? null),
724
+ ],
725
+ ([currentTools, preloadKey], _previous, cleanup) => {
726
+ let cancelled = false;
727
+ cleanup(() => {
728
+ cancelled = true;
729
+ });
730
+ const preload: WidgetInstructionsOptions["preload"] | null =
731
+ JSON.parse(preloadKey);
732
+ void buildWidgetInstructions(
733
+ currentTools,
734
+ preload ? { preload } : {},
735
+ ).then((text) => {
736
+ if (!cancelled) instruction.value = text;
737
+ });
738
+ },
739
+ { immediate: true },
740
+ ),
741
+ [],
742
+ );
743
+ useAssistantInstructions(() => ({
744
+ instruction: instruction.value,
745
+ disabled: !instruction.value,
746
+ }));
747
+ }