@assistant-ui/react-generative-ui 0.0.24 → 0.1.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 (338) hide show
  1. package/README.md +2 -0
  2. package/dist/a2ui.d.ts +1 -4
  3. package/dist/a2ui.d.ts.map +1 -1
  4. package/dist/a2ui.js +1 -4
  5. package/dist/index.d.ts +1 -2
  6. package/dist/index.d.ts.map +1 -1
  7. package/dist/index.js +1 -10
  8. package/dist/index.server.d.ts +1 -2
  9. package/dist/index.server.d.ts.map +1 -1
  10. package/dist/index.server.js +1 -10
  11. package/dist/ir.d.ts +1 -111
  12. package/dist/ir.d.ts.map +1 -1
  13. package/dist/ir.js +1 -175
  14. package/dist/slack.d.ts +1 -4
  15. package/dist/slack.d.ts.map +1 -1
  16. package/dist/slack.js +1 -4
  17. package/dist/teams.d.ts +1 -4
  18. package/dist/teams.d.ts.map +1 -1
  19. package/dist/teams.js +1 -4
  20. package/package.json +4 -5
  21. package/src/a2ui.ts +1 -22
  22. package/src/index.server.ts +1 -2
  23. package/src/index.test.ts +75 -0
  24. package/src/index.ts +1 -2
  25. package/src/ir.ts +1 -279
  26. package/src/slack.ts +1 -39
  27. package/src/teams.ts +1 -35
  28. package/dist/JSONGenerativeUI.client.d.ts +0 -28
  29. package/dist/JSONGenerativeUI.client.d.ts.map +0 -1
  30. package/dist/JSONGenerativeUI.client.js +0 -101
  31. package/dist/JSONGenerativeUI.client.js.map +0 -1
  32. package/dist/JSONGenerativeUI.server.d.ts +0 -21
  33. package/dist/JSONGenerativeUI.server.d.ts.map +0 -1
  34. package/dist/JSONGenerativeUI.server.js +0 -32
  35. package/dist/JSONGenerativeUI.server.js.map +0 -1
  36. package/dist/JSONGenerativeUI.shared.d.ts +0 -63
  37. package/dist/JSONGenerativeUI.shared.d.ts.map +0 -1
  38. package/dist/JSONGenerativeUI.shared.js +0 -31
  39. package/dist/JSONGenerativeUI.shared.js.map +0 -1
  40. package/dist/RadioGroupScope.d.ts +0 -6
  41. package/dist/RadioGroupScope.d.ts.map +0 -1
  42. package/dist/RadioGroupScope.js +0 -25
  43. package/dist/RadioGroupScope.js.map +0 -1
  44. package/dist/a2ui/BindingContext.d.ts +0 -11
  45. package/dist/a2ui/BindingContext.d.ts.map +0 -1
  46. package/dist/a2ui/BindingContext.js +0 -12
  47. package/dist/a2ui/BindingContext.js.map +0 -1
  48. package/dist/a2ui/PresentRenderer.d.ts +0 -11
  49. package/dist/a2ui/PresentRenderer.d.ts.map +0 -1
  50. package/dist/a2ui/PresentRenderer.js +0 -39
  51. package/dist/a2ui/PresentRenderer.js.map +0 -1
  52. package/dist/a2ui/convert.d.ts +0 -15
  53. package/dist/a2ui/convert.d.ts.map +0 -1
  54. package/dist/a2ui/convert.js +0 -728
  55. package/dist/a2ui/convert.js.map +0 -1
  56. package/dist/a2ui/dataModel.d.ts +0 -9
  57. package/dist/a2ui/dataModel.d.ts.map +0 -1
  58. package/dist/a2ui/dataModel.js +0 -44
  59. package/dist/a2ui/dataModel.js.map +0 -1
  60. package/dist/a2ui/reducer.d.ts +0 -8
  61. package/dist/a2ui/reducer.d.ts.map +0 -1
  62. package/dist/a2ui/reducer.js +0 -242
  63. package/dist/a2ui/reducer.js.map +0 -1
  64. package/dist/a2ui/snapshot.d.ts +0 -3
  65. package/dist/a2ui/snapshot.d.ts.map +0 -1
  66. package/dist/a2ui/snapshot.js +0 -33
  67. package/dist/a2ui/snapshot.js.map +0 -1
  68. package/dist/a2ui/types.d.ts +0 -88
  69. package/dist/a2ui/types.d.ts.map +0 -1
  70. package/dist/a2ui/types.js +0 -6
  71. package/dist/a2ui/types.js.map +0 -1
  72. package/dist/a2ui/valueFunctions.d.ts +0 -16
  73. package/dist/a2ui/valueFunctions.d.ts.map +0 -1
  74. package/dist/a2ui/valueFunctions.js +0 -332
  75. package/dist/a2ui/valueFunctions.js.map +0 -1
  76. package/dist/actionRegistry.d.ts +0 -29
  77. package/dist/actionRegistry.d.ts.map +0 -1
  78. package/dist/actionRegistry.js +0 -41
  79. package/dist/actionRegistry.js.map +0 -1
  80. package/dist/answeredValues.d.ts +0 -16
  81. package/dist/answeredValues.d.ts.map +0 -1
  82. package/dist/answeredValues.js +0 -37
  83. package/dist/answeredValues.js.map +0 -1
  84. package/dist/buildPresentParameters.d.ts +0 -21
  85. package/dist/buildPresentParameters.d.ts.map +0 -1
  86. package/dist/buildPresentParameters.js +0 -95
  87. package/dist/buildPresentParameters.js.map +0 -1
  88. package/dist/constants.d.ts +0 -15
  89. package/dist/constants.d.ts.map +0 -1
  90. package/dist/constants.js +0 -19
  91. package/dist/constants.js.map +0 -1
  92. package/dist/convert/boundSpec.d.ts +0 -48
  93. package/dist/convert/boundSpec.d.ts.map +0 -1
  94. package/dist/convert/boundSpec.js +0 -108
  95. package/dist/convert/boundSpec.js.map +0 -1
  96. package/dist/convert/copyBounded.d.ts +0 -25
  97. package/dist/convert/copyBounded.d.ts.map +0 -1
  98. package/dist/convert/copyBounded.js +0 -38
  99. package/dist/convert/copyBounded.js.map +0 -1
  100. package/dist/convert/isElement.d.ts +0 -3
  101. package/dist/convert/isElement.d.ts.map +0 -1
  102. package/dist/convert/isElement.js +0 -6
  103. package/dist/convert/isElement.js.map +0 -1
  104. package/dist/convert/takeRun.d.ts +0 -6
  105. package/dist/convert/takeRun.d.ts.map +0 -1
  106. package/dist/convert/takeRun.js +0 -20
  107. package/dist/convert/takeRun.js.map +0 -1
  108. package/dist/defineGenerativeComponents.d.ts +0 -29
  109. package/dist/defineGenerativeComponents.d.ts.map +0 -1
  110. package/dist/defineGenerativeComponents.js +0 -34
  111. package/dist/defineGenerativeComponents.js.map +0 -1
  112. package/dist/fieldReferences.d.ts +0 -9
  113. package/dist/fieldReferences.d.ts.map +0 -1
  114. package/dist/fieldReferences.js +0 -53
  115. package/dist/fieldReferences.js.map +0 -1
  116. package/dist/generativeUIToJSX.d.ts +0 -18
  117. package/dist/generativeUIToJSX.d.ts.map +0 -1
  118. package/dist/generativeUIToJSX.js +0 -76
  119. package/dist/generativeUIToJSX.js.map +0 -1
  120. package/dist/index.shared.d.ts +0 -15
  121. package/dist/index.shared.d.ts.map +0 -1
  122. package/dist/index.shared.js +0 -9
  123. package/dist/ir.js.map +0 -1
  124. package/dist/renderGenerativeUI.d.ts +0 -14
  125. package/dist/renderGenerativeUI.d.ts.map +0 -1
  126. package/dist/renderGenerativeUI.js +0 -83
  127. package/dist/renderGenerativeUI.js.map +0 -1
  128. package/dist/scopeSchema.d.ts +0 -6
  129. package/dist/scopeSchema.d.ts.map +0 -1
  130. package/dist/scopeSchema.js +0 -56
  131. package/dist/scopeSchema.js.map +0 -1
  132. package/dist/slack/constants.d.ts +0 -93
  133. package/dist/slack/constants.d.ts.map +0 -1
  134. package/dist/slack/constants.js +0 -146
  135. package/dist/slack/constants.js.map +0 -1
  136. package/dist/slack/decodeBlockAction.d.ts +0 -10
  137. package/dist/slack/decodeBlockAction.d.ts.map +0 -1
  138. package/dist/slack/decodeBlockAction.js +0 -129
  139. package/dist/slack/decodeBlockAction.js.map +0 -1
  140. package/dist/slack/fromSlackBlocks.d.ts +0 -27
  141. package/dist/slack/fromSlackBlocks.d.ts.map +0 -1
  142. package/dist/slack/fromSlackBlocks.js +0 -483
  143. package/dist/slack/fromSlackBlocks.js.map +0 -1
  144. package/dist/slack/toSlackBlocks.d.ts +0 -4
  145. package/dist/slack/toSlackBlocks.d.ts.map +0 -1
  146. package/dist/slack/toSlackBlocks.js +0 -901
  147. package/dist/slack/toSlackBlocks.js.map +0 -1
  148. package/dist/slack/types.d.ts +0 -204
  149. package/dist/slack/types.d.ts.map +0 -1
  150. package/dist/slack/types.js +0 -0
  151. package/dist/teams/constants.d.ts +0 -34
  152. package/dist/teams/constants.d.ts.map +0 -1
  153. package/dist/teams/constants.js +0 -68
  154. package/dist/teams/constants.js.map +0 -1
  155. package/dist/teams/decodeSubmitData.d.ts +0 -26
  156. package/dist/teams/decodeSubmitData.d.ts.map +0 -1
  157. package/dist/teams/decodeSubmitData.js +0 -70
  158. package/dist/teams/decodeSubmitData.js.map +0 -1
  159. package/dist/teams/temporalId.d.ts +0 -5
  160. package/dist/teams/temporalId.d.ts.map +0 -1
  161. package/dist/teams/temporalId.js +0 -28
  162. package/dist/teams/temporalId.js.map +0 -1
  163. package/dist/teams/toAdaptiveCard.d.ts +0 -45
  164. package/dist/teams/toAdaptiveCard.d.ts.map +0 -1
  165. package/dist/teams/toAdaptiveCard.js +0 -642
  166. package/dist/teams/toAdaptiveCard.js.map +0 -1
  167. package/dist/teams/toTeamsAttachments.d.ts +0 -10
  168. package/dist/teams/toTeamsAttachments.d.ts.map +0 -1
  169. package/dist/teams/toTeamsAttachments.js +0 -99
  170. package/dist/teams/toTeamsAttachments.js.map +0 -1
  171. package/dist/teams/types.d.ts +0 -224
  172. package/dist/teams/types.d.ts.map +0 -1
  173. package/dist/teams/types.js +0 -0
  174. package/dist/temporal.d.ts +0 -33
  175. package/dist/temporal.d.ts.map +0 -1
  176. package/dist/temporal.js +0 -153
  177. package/dist/temporal.js.map +0 -1
  178. package/dist/types.d.ts +0 -99
  179. package/dist/types.d.ts.map +0 -1
  180. package/dist/types.js +0 -0
  181. package/dist/vocabulary/alert.d.ts +0 -32
  182. package/dist/vocabulary/alert.d.ts.map +0 -1
  183. package/dist/vocabulary/alert.js +0 -160
  184. package/dist/vocabulary/alert.js.map +0 -1
  185. package/dist/vocabulary/collectFormValues.d.ts +0 -23
  186. package/dist/vocabulary/collectFormValues.d.ts.map +0 -1
  187. package/dist/vocabulary/collectFormValues.js +0 -44
  188. package/dist/vocabulary/collectFormValues.js.map +0 -1
  189. package/dist/vocabulary/data.d.ts +0 -66
  190. package/dist/vocabulary/data.d.ts.map +0 -1
  191. package/dist/vocabulary/data.js +0 -503
  192. package/dist/vocabulary/data.js.map +0 -1
  193. package/dist/vocabulary/dispatch.d.ts +0 -9
  194. package/dist/vocabulary/dispatch.d.ts.map +0 -1
  195. package/dist/vocabulary/dispatch.js +0 -50
  196. package/dist/vocabulary/dispatch.js.map +0 -1
  197. package/dist/vocabulary/fact.d.ts +0 -19
  198. package/dist/vocabulary/fact.d.ts.map +0 -1
  199. package/dist/vocabulary/fact.js +0 -51
  200. package/dist/vocabulary/fact.js.map +0 -1
  201. package/dist/vocabulary/form.d.ts +0 -11
  202. package/dist/vocabulary/form.d.ts.map +0 -1
  203. package/dist/vocabulary/form.js +0 -23
  204. package/dist/vocabulary/form.js.map +0 -1
  205. package/dist/vocabulary/formatValue.d.ts +0 -7
  206. package/dist/vocabulary/formatValue.d.ts.map +0 -1
  207. package/dist/vocabulary/formatValue.js +0 -76
  208. package/dist/vocabulary/formatValue.js.map +0 -1
  209. package/dist/vocabulary/icon.d.ts +0 -41
  210. package/dist/vocabulary/icon.d.ts.map +0 -1
  211. package/dist/vocabulary/icon.js +0 -272
  212. package/dist/vocabulary/icon.js.map +0 -1
  213. package/dist/vocabulary/index.d.ts +0 -12
  214. package/dist/vocabulary/index.d.ts.map +0 -1
  215. package/dist/vocabulary/index.js +0 -36
  216. package/dist/vocabulary/index.js.map +0 -1
  217. package/dist/vocabulary/interactive.d.ts +0 -230
  218. package/dist/vocabulary/interactive.d.ts.map +0 -1
  219. package/dist/vocabulary/interactive.js +0 -584
  220. package/dist/vocabulary/interactive.js.map +0 -1
  221. package/dist/vocabulary/layout.d.ts +0 -79
  222. package/dist/vocabulary/layout.d.ts.map +0 -1
  223. package/dist/vocabulary/layout.js +0 -139
  224. package/dist/vocabulary/layout.js.map +0 -1
  225. package/dist/vocabulary/list.d.ts +0 -14
  226. package/dist/vocabulary/list.d.ts.map +0 -1
  227. package/dist/vocabulary/list.js +0 -50
  228. package/dist/vocabulary/list.js.map +0 -1
  229. package/dist/vocabulary/media.d.ts +0 -25
  230. package/dist/vocabulary/media.d.ts.map +0 -1
  231. package/dist/vocabulary/media.js +0 -42
  232. package/dist/vocabulary/media.js.map +0 -1
  233. package/dist/vocabulary/text.d.ts +0 -57
  234. package/dist/vocabulary/text.d.ts.map +0 -1
  235. package/dist/vocabulary/text.js +0 -49
  236. package/dist/vocabulary/text.js.map +0 -1
  237. package/dist/vocabulary/toTextContent.d.ts +0 -2
  238. package/dist/vocabulary/toTextContent.d.ts.map +0 -1
  239. package/dist/vocabulary/toTextContent.js +0 -6
  240. package/dist/vocabulary/toTextContent.js.map +0 -1
  241. package/src/JSONGenerativeUI.client.tsx +0 -168
  242. package/src/JSONGenerativeUI.server.tsx +0 -39
  243. package/src/JSONGenerativeUI.shared.ts +0 -90
  244. package/src/JSONGenerativeUI.test.tsx +0 -1298
  245. package/src/RadioGroupScope.server.test.tsx +0 -24
  246. package/src/RadioGroupScope.tsx +0 -34
  247. package/src/a2ui/BindingContext.ts +0 -26
  248. package/src/a2ui/PresentRenderer.tsx +0 -83
  249. package/src/a2ui/convert.test.ts +0 -2923
  250. package/src/a2ui/convert.ts +0 -1207
  251. package/src/a2ui/dataModel.ts +0 -69
  252. package/src/a2ui/reducer.test.ts +0 -422
  253. package/src/a2ui/reducer.ts +0 -363
  254. package/src/a2ui/snapshot.test.ts +0 -52
  255. package/src/a2ui/snapshot.ts +0 -52
  256. package/src/a2ui/types.ts +0 -114
  257. package/src/a2ui/valueFunctions.test.ts +0 -271
  258. package/src/a2ui/valueFunctions.ts +0 -523
  259. package/src/actionRegistry.test.ts +0 -90
  260. package/src/actionRegistry.ts +0 -88
  261. package/src/answeredValues.test.ts +0 -33
  262. package/src/answeredValues.tsx +0 -70
  263. package/src/buildPresentParameters.test.ts +0 -246
  264. package/src/buildPresentParameters.ts +0 -130
  265. package/src/constants.ts +0 -17
  266. package/src/convert/boundSpec.test.ts +0 -192
  267. package/src/convert/boundSpec.ts +0 -134
  268. package/src/convert/copyBounded.test.ts +0 -131
  269. package/src/convert/copyBounded.ts +0 -38
  270. package/src/convert/isElement.ts +0 -6
  271. package/src/convert/takeRun.test.ts +0 -64
  272. package/src/convert/takeRun.ts +0 -19
  273. package/src/defineGenerativeComponents.ts +0 -38
  274. package/src/fieldReferences.test.ts +0 -66
  275. package/src/fieldReferences.ts +0 -83
  276. package/src/generativeUIToJSX.test.ts +0 -268
  277. package/src/generativeUIToJSX.ts +0 -152
  278. package/src/index.server.test.ts +0 -106
  279. package/src/index.shared.ts +0 -61
  280. package/src/ir.test.ts +0 -203
  281. package/src/renderGenerativeUI.test.tsx +0 -373
  282. package/src/renderGenerativeUI.tsx +0 -165
  283. package/src/scopeSchema.test.ts +0 -72
  284. package/src/scopeSchema.ts +0 -71
  285. package/src/slack/a2uiSlack.test.ts +0 -198
  286. package/src/slack/constants.ts +0 -194
  287. package/src/slack/datePicker.test.ts +0 -612
  288. package/src/slack/decodeBlockAction.test.ts +0 -233
  289. package/src/slack/decodeBlockAction.ts +0 -232
  290. package/src/slack/fromSlackBlocks.test.ts +0 -1465
  291. package/src/slack/fromSlackBlocks.ts +0 -789
  292. package/src/slack/toSlackBlocks.test.ts +0 -3065
  293. package/src/slack/toSlackBlocks.ts +0 -1818
  294. package/src/slack/types.ts +0 -264
  295. package/src/teams/constants.ts +0 -93
  296. package/src/teams/decodeSubmitData.test.ts +0 -397
  297. package/src/teams/decodeSubmitData.ts +0 -110
  298. package/src/teams/temporal.test.ts +0 -766
  299. package/src/teams/temporalId.ts +0 -56
  300. package/src/teams/toAdaptiveCard.test.ts +0 -1897
  301. package/src/teams/toAdaptiveCard.ts +0 -1057
  302. package/src/teams/toTeamsAttachments.test.ts +0 -74
  303. package/src/teams/toTeamsAttachments.ts +0 -133
  304. package/src/teams/types.ts +0 -266
  305. package/src/temporal.test.ts +0 -337
  306. package/src/temporal.ts +0 -263
  307. package/src/types.ts +0 -119
  308. package/src/vocabulary/alert.test.tsx +0 -427
  309. package/src/vocabulary/alert.tsx +0 -217
  310. package/src/vocabulary/collectFormValues.test.ts +0 -389
  311. package/src/vocabulary/collectFormValues.ts +0 -116
  312. package/src/vocabulary/data.test.tsx +0 -858
  313. package/src/vocabulary/data.tsx +0 -819
  314. package/src/vocabulary/datepicker.dom.test.tsx +0 -1457
  315. package/src/vocabulary/dispatch.ts +0 -83
  316. package/src/vocabulary/fact.test.tsx +0 -94
  317. package/src/vocabulary/fact.tsx +0 -54
  318. package/src/vocabulary/form.test.tsx +0 -113
  319. package/src/vocabulary/form.tsx +0 -39
  320. package/src/vocabulary/formatValue.test.ts +0 -72
  321. package/src/vocabulary/formatValue.ts +0 -127
  322. package/src/vocabulary/icon.test.tsx +0 -64
  323. package/src/vocabulary/icon.tsx +0 -174
  324. package/src/vocabulary/index.test.ts +0 -65
  325. package/src/vocabulary/index.ts +0 -33
  326. package/src/vocabulary/interactive.dispatch.test.tsx +0 -454
  327. package/src/vocabulary/interactive.dom.test.tsx +0 -1747
  328. package/src/vocabulary/interactive.test.tsx +0 -377
  329. package/src/vocabulary/interactive.tsx +0 -1109
  330. package/src/vocabulary/layout.test.tsx +0 -323
  331. package/src/vocabulary/layout.tsx +0 -247
  332. package/src/vocabulary/list.test.tsx +0 -222
  333. package/src/vocabulary/list.tsx +0 -50
  334. package/src/vocabulary/media.test.tsx +0 -85
  335. package/src/vocabulary/media.tsx +0 -56
  336. package/src/vocabulary/text.test.tsx +0 -102
  337. package/src/vocabulary/text.tsx +0 -60
  338. package/src/vocabulary/toTextContent.ts +0 -2
@@ -1,165 +0,0 @@
1
- import { RadioGroupScope } from "./RadioGroupScope";
2
- import { getPartialJsonObjectMeta } from "assistant-stream/utils";
3
- import { Fragment, type ReactNode } from "react";
4
- import { hasFieldReference, resolveFieldReferences } from "./fieldReferences";
5
- import {
6
- normalizeUINode,
7
- type Action,
8
- type NormalizedUIElement,
9
- type NormalizedUINode,
10
- } from "./ir";
11
- import type { GenerativeUILibrary, GenerativeUIRenderContext } from "./types";
12
-
13
- const DEFAULT_CONTEXT: GenerativeUIRenderContext = { status: "done" };
14
-
15
- const isElement = (node: NormalizedUINode): node is NormalizedUIElement =>
16
- typeof node === "object" && node !== null && !Array.isArray(node);
17
-
18
- /**
19
- * Renders a generative-ui tree against a {@link GenerativeUILibrary}.
20
- *
21
- * The model emits each node as a flat object `{ $type, ...props }`. We first
22
- * normalize that wire form into the canonical {@link NormalizedUINode} (with
23
- * `children` lifted to a reserved top-level key), then render: each `type` is
24
- * looked up in the library and its `props` are passed to the component's
25
- * `render(props, context)`, with `children` rendered recursively so components
26
- * can nest.
27
- */
28
- export function renderGenerativeUI(
29
- node: unknown,
30
- library: GenerativeUILibrary,
31
- context: GenerativeUIRenderContext = DEFAULT_CONTEXT,
32
- ): ReactNode {
33
- // Tool args are parsed incrementally, and the parse meta records which path
34
- // is still mid-arrival, so normalization can hold back a node whose `$type`
35
- // string has not finished streaming.
36
- const meta = getPartialJsonObjectMeta(node as Record<symbol, unknown>);
37
- const partialPath = meta?.state === "partial" ? meta.partialPath : undefined;
38
- const rendered = renderNode(
39
- normalizeUINode(node, partialPath),
40
- library,
41
- withFieldFallbacks(context),
42
- );
43
- return rendered !== null && typeof rendered === "object" ? (
44
- <RadioGroupScope>{rendered}</RadioGroupScope>
45
- ) : (
46
- rendered
47
- );
48
- }
49
-
50
- // A component that dispatches `$action` itself reads no control, so each `$field` reference left in it becomes its fallback.
51
- const withFieldFallbacks = (
52
- context: GenerativeUIRenderContext,
53
- ): GenerativeUIRenderContext => {
54
- const { dispatch } = context;
55
- if (dispatch === undefined) return context;
56
- return {
57
- ...context,
58
- dispatch: (action) =>
59
- dispatch(
60
- hasFieldReference(action)
61
- ? (resolveFieldReferences(action, {}) as Action)
62
- : action,
63
- ),
64
- };
65
- };
66
-
67
- function renderNode(
68
- node: NormalizedUINode,
69
- library: GenerativeUILibrary,
70
- context: GenerativeUIRenderContext,
71
- ): ReactNode {
72
- if (node == null || typeof node === "boolean") return null;
73
- if (typeof node === "string" || typeof node === "number") return node;
74
- if (Array.isArray(node)) {
75
- // Use a model-provided stable key when present. Otherwise, keep the
76
- // positional fallback: pairing the index with the node's kind means that
77
- // when the kind at an index changes, React remounts instead of handing a
78
- // streaming node's hook state to a different component.
79
- return node.map((child, index) => (
80
- <Fragment key={nodeKey(child, index)}>
81
- {renderNode(child, library, context)}
82
- </Fragment>
83
- ));
84
- }
85
- return isElement(node) ? renderElement(node, library, context) : null;
86
- }
87
-
88
- function renderElement(
89
- element: NormalizedUIElement,
90
- library: GenerativeUILibrary,
91
- context: GenerativeUIRenderContext,
92
- ): ReactNode {
93
- const entry = Object.hasOwn(library, element.type)
94
- ? library[element.type]
95
- : undefined;
96
- if (!entry) {
97
- reportUnknownComponent(element.type, Object.keys(library));
98
- return null;
99
- }
100
-
101
- // Components that opt out of prop streaming wait until their props are
102
- // complete rather than rendering from a partial parse.
103
- if (!entry.streamProperties && context.status === "streaming") return null;
104
-
105
- // `children` is a reserved top-level key on the normalized element, not a
106
- // prop, so it is not in `props`. Inject the framework props last so the model
107
- // can never override them. `$action` is likewise reserved and stripped from
108
- // the prop bag during normalization, so it is re-injected here for components
109
- // that carry behavior (e.g. `Button`).
110
- const props: Record<string, unknown> = {
111
- ...element.props,
112
- $status: context.status,
113
- };
114
- if (context.dispatch !== undefined) {
115
- props["$dispatch"] = context.dispatch;
116
- }
117
- if (element.action !== undefined) {
118
- props["$action"] = element.action;
119
- }
120
- if (element.children !== undefined) {
121
- props["children"] = renderNode(element.children, library, context);
122
- }
123
-
124
- return <GenerativeUIComponentRenderer render={entry.render} props={props} />;
125
- }
126
-
127
- /**
128
- * Mounts a single node's `render` on its own fiber so the function may use
129
- * hooks and hold state independently of its siblings and parent.
130
- */
131
- function GenerativeUIComponentRenderer({
132
- render,
133
- props,
134
- }: {
135
- render: (props: any) => ReactNode;
136
- props: Record<string, unknown>;
137
- }): ReactNode {
138
- return render(props);
139
- }
140
-
141
- /** A coarse kind tag for a child, used in its list key so a node changing kind
142
- * at a given index forces a remount rather than a wrong-fiber reuse. */
143
- function nodeKind(node: NormalizedUINode): string {
144
- if (node == null || typeof node === "boolean") return "";
145
- if (typeof node === "string" || typeof node === "number") return "#text";
146
- if (Array.isArray(node)) return "#array";
147
- return isElement(node) ? node.type : "";
148
- }
149
-
150
- function nodeKey(node: NormalizedUINode, index: number): string {
151
- return isElement(node) &&
152
- (typeof node.key === "string" || typeof node.key === "number")
153
- ? `model:${node.key}`
154
- : `${index}:${nodeKind(node)}`;
155
- }
156
-
157
- function reportUnknownComponent(type: string, available: string[]): void {
158
- if (process.env["NODE_ENV"] !== "production") {
159
- // eslint-disable-next-line no-console
160
- console.error(
161
- `[@assistant-ui/react-generative-ui] Unknown component "${type}". ` +
162
- `Available components: ${available.join(", ") || "(none)"}.`,
163
- );
164
- }
165
- }
@@ -1,72 +0,0 @@
1
- import type { JSONSchema7 } from "json-schema";
2
- import { describe, expect, it } from "vitest";
3
- import { scopeSchema } from "./scopeSchema";
4
-
5
- const ref = { $ref: "#/definitions/a~1b~0c" };
6
- const scopedRef = { $ref: "#/$defs/component0/definitions/a~1b~0c" };
7
- const scope = (schema: JSONSchema7) =>
8
- scopeSchema(schema, "#/$defs/component0");
9
-
10
- describe("scopeSchema", () => {
11
- it.each([
12
- "properties",
13
- "patternProperties",
14
- "definitions",
15
- "$defs",
16
- "dependentSchemas",
17
- ])("rebases schemas inside %s without changing the input", (keyword) => {
18
- const original = {
19
- [keyword]: { value: ref, allowed: true, forbidden: false },
20
- };
21
- const result = scope(original);
22
- expect(result.schema).toEqual({
23
- [keyword]: { value: scopedRef, allowed: true, forbidden: false },
24
- });
25
- expect(result.referenced).toBe(true);
26
- expect(original).toEqual({
27
- [keyword]: { value: ref, allowed: true, forbidden: false },
28
- });
29
- });
30
- it.each(["allOf", "anyOf", "oneOf", "items", "prefixItems"])(
31
- "rebases schemas in %s arrays",
32
- (keyword) => {
33
- expect(scope({ [keyword]: [ref, true, false] }).schema).toEqual({
34
- [keyword]: [scopedRef, true, false],
35
- });
36
- },
37
- );
38
- it.each([
39
- "items",
40
- "additionalProperties",
41
- "additionalItems",
42
- "unevaluatedProperties",
43
- "unevaluatedItems",
44
- "contentSchema",
45
- "contains",
46
- "propertyNames",
47
- "not",
48
- "if",
49
- "then",
50
- "else",
51
- ])("rebases the %s subschema", (keyword) => {
52
- expect(scope({ [keyword]: ref }).schema).toEqual({ [keyword]: scopedRef });
53
- });
54
- it("preserves property dependencies while rebasing schema dependencies", () => {
55
- expect(
56
- scope({ dependencies: { first: ["second"], other: ref } }).schema,
57
- ).toEqual({ dependencies: { first: ["second"], other: scopedRef } });
58
- });
59
- it("leaves literal data, external references, and named anchors unchanged", () => {
60
- const schema: JSONSchema7 = {
61
- default: ref,
62
- examples: [ref],
63
- enum: [ref],
64
- const: ref,
65
- properties: {
66
- remote: { $ref: "https://example.com/schema.json#/definitions/value" },
67
- anchor: { $ref: "#value" },
68
- },
69
- };
70
- expect(scope(schema)).toEqual({ schema, referenced: false });
71
- });
72
- });
@@ -1,71 +0,0 @@
1
- import type { JSONSchema7, JSONSchema7Definition } from "json-schema";
2
-
3
- // The converter does not pin a JSON Schema draft, so the 2020-12 applicators
4
- // missing from the JSONSchema7 typings must be traversed as well.
5
- type TraversableSchema = JSONSchema7 & {
6
- prefixItems?: JSONSchema7Definition[];
7
- dependentSchemas?: { [key: string]: JSONSchema7Definition };
8
- unevaluatedItems?: JSONSchema7Definition;
9
- unevaluatedProperties?: JSONSchema7Definition;
10
- contentSchema?: JSONSchema7Definition;
11
- };
12
-
13
- export function scopeSchema(schema: JSONSchema7, path: string) {
14
- let referenced = false;
15
- const visit = (definition: JSONSchema7Definition): JSONSchema7Definition => {
16
- if (typeof definition === "boolean") return definition;
17
- const schema = definition as TraversableSchema;
18
- const result = { ...schema };
19
- if (schema.$ref === "#" || schema.$ref?.startsWith("#/")) {
20
- referenced = true;
21
- result.$ref = path + schema.$ref.slice(1);
22
- }
23
- // Only schema-bearing keywords are traversed; defaults and examples are data.
24
- for (const key of [
25
- "properties",
26
- "patternProperties",
27
- "definitions",
28
- "$defs",
29
- "dependentSchemas",
30
- ] as const) {
31
- const entries = schema[key];
32
- if (entries)
33
- result[key] = Object.fromEntries(
34
- Object.entries(entries).map(([name, value]) => [name, visit(value)]),
35
- );
36
- }
37
- for (const key of ["allOf", "anyOf", "oneOf", "prefixItems"] as const) {
38
- if (schema[key]) result[key] = schema[key].map(visit);
39
- }
40
- for (const key of [
41
- "additionalProperties",
42
- "additionalItems",
43
- "unevaluatedProperties",
44
- "unevaluatedItems",
45
- "contentSchema",
46
- "contains",
47
- "propertyNames",
48
- "not",
49
- "if",
50
- "then",
51
- "else",
52
- ] as const) {
53
- const value = schema[key];
54
- if (value !== undefined) result[key] = visit(value);
55
- }
56
- if (schema.items !== undefined)
57
- result.items = Array.isArray(schema.items)
58
- ? schema.items.map(visit)
59
- : visit(schema.items);
60
- if (schema.dependencies)
61
- result.dependencies = Object.fromEntries(
62
- Object.entries(schema.dependencies).map(([name, value]) => [
63
- name,
64
- Array.isArray(value) ? value : visit(value),
65
- ]),
66
- );
67
- return result;
68
- };
69
- const scoped = visit(schema) as JSONSchema7;
70
- return { schema: scoped, referenced };
71
- }
@@ -1,198 +0,0 @@
1
- import { describe, expect, it } from "vitest";
2
- import { convertSurfaceToUISpec } from "../a2ui/convert";
3
- import { decodeBlockAction } from "./decodeBlockAction";
4
- import { toSlackBlocks } from "./toSlackBlocks";
5
-
6
- const convertForm = () => {
7
- const { spec, warnings: a2uiWarnings } = convertSurfaceToUISpec({
8
- components: new Map([
9
- ["root", { id: "root", component: "Column", children: ["name", "send"] }],
10
- [
11
- "name",
12
- {
13
- id: "name",
14
- component: "TextField",
15
- label: "Name",
16
- value: { path: "/form/name" },
17
- },
18
- ],
19
- [
20
- "send",
21
- {
22
- id: "send",
23
- component: "Button",
24
- label: "Send",
25
- action: {
26
- event: {
27
- name: "send",
28
- context: { name: { path: "/form/name" } },
29
- },
30
- },
31
- },
32
- ],
33
- ]),
34
- dataModel: { form: { name: "Ada" } },
35
- });
36
-
37
- expect(a2uiWarnings).toEqual([]);
38
- expect(spec).toMatchObject({
39
- children: [
40
- { $type: "Input", name: "/form/name", defaultValue: "Ada" },
41
- {
42
- $type: "Button",
43
- $action: {
44
- context: { name: { $field: "/form/name", fallback: "Ada" } },
45
- },
46
- },
47
- ],
48
- });
49
- if (!spec) throw new Error("Expected an A2UI tree");
50
-
51
- const { blocks, warnings: slackWarnings } = toSlackBlocks(spec);
52
- expect(slackWarnings).toEqual([]);
53
- const input = blocks.find((block) => block.type === "input");
54
- const actions = blocks.find((block) => block.type === "actions");
55
- if (!input || input.type !== "input" || !input.block_id)
56
- throw new Error("Expected a named Slack input block");
57
- if (!actions || actions.type !== "actions")
58
- throw new Error("Expected a Slack actions block");
59
- const button = actions.elements[0];
60
- if (!button || button.type !== "button")
61
- throw new Error("Expected a Slack button");
62
-
63
- return { blockId: input.block_id, actionId: input.element.action_id, button };
64
- };
65
-
66
- describe("A2UI actions on Slack", () => {
67
- it("resolves a bound CheckBox as a boolean when a button is pressed", () => {
68
- const { spec, warnings } = convertSurfaceToUISpec({
69
- components: new Map([
70
- [
71
- "root",
72
- { id: "root", component: "Column", children: ["accepted", "send"] },
73
- ],
74
- [
75
- "accepted",
76
- {
77
- id: "accepted",
78
- component: "CheckBox",
79
- label: "Accepted",
80
- value: { path: "/form/accepted" },
81
- },
82
- ],
83
- [
84
- "send",
85
- {
86
- id: "send",
87
- component: "Button",
88
- label: "Send",
89
- action: {
90
- event: {
91
- name: "send",
92
- context: { accepted: { path: "/form/accepted" } },
93
- },
94
- },
95
- },
96
- ],
97
- ]),
98
- dataModel: { form: { accepted: true } },
99
- });
100
- expect(warnings).toEqual([]);
101
- if (!spec) throw new Error("Expected an A2UI tree");
102
- const { blocks, warnings: slackWarnings } = toSlackBlocks(spec);
103
- expect(slackWarnings).toEqual([]);
104
- const checkboxBlock = blocks.find(
105
- (block) =>
106
- block.type === "actions" &&
107
- block.elements.some((element) => element.type === "checkboxes"),
108
- );
109
- const buttonBlock = blocks.find(
110
- (block) =>
111
- block.type === "actions" &&
112
- block.elements.some((element) => element.type === "button"),
113
- );
114
- if (
115
- !checkboxBlock ||
116
- checkboxBlock.type !== "actions" ||
117
- !checkboxBlock.block_id ||
118
- !buttonBlock ||
119
- buttonBlock.type !== "actions"
120
- ) {
121
- throw new Error("Expected named checkbox and button blocks");
122
- }
123
- const checkbox = checkboxBlock.elements.find(
124
- (element) => element.type === "checkboxes",
125
- );
126
- const button = buttonBlock.elements.find(
127
- (element) => element.type === "button",
128
- );
129
- if (!checkbox || checkbox.type !== "checkboxes" || !button) {
130
- throw new Error("Expected a checkbox and button");
131
- }
132
-
133
- for (const [selectedOptions, accepted] of [
134
- [[], false],
135
- [checkbox.options[0] ? [checkbox.options[0]] : [], true],
136
- ] as const) {
137
- expect(
138
- decodeBlockAction(button, {
139
- [checkboxBlock.block_id]: {
140
- [checkbox.action_id]: {
141
- type: "checkboxes",
142
- selected_options: selectedOptions,
143
- },
144
- },
145
- }),
146
- ).toMatchObject({ context: { accepted } });
147
- }
148
- });
149
-
150
- it("decodes a named Checkbox action with a list in $input", () => {
151
- const blockId = `aui:0:${JSON.stringify([["toggle", "agree", "Checkbox"]])}`;
152
- for (const [selectedOptions, input] of [
153
- [[], []],
154
- [[{ value: "agree" }], ["agree"]],
155
- ] as const) {
156
- expect(
157
- decodeBlockAction({
158
- type: "checkboxes",
159
- block_id: blockId,
160
- action_id: "toggle",
161
- selected_options: selectedOptions,
162
- }),
163
- ).toEqual({ type: "toggle", $input: input });
164
- }
165
- });
166
-
167
- it("sends the user's edited TextField value", () => {
168
- const { blockId, actionId, button } = convertForm();
169
- const stateValues = {
170
- [blockId]: {
171
- [actionId]: {
172
- type: "plain_text_input",
173
- value: "Grace",
174
- },
175
- },
176
- };
177
-
178
- expect(decodeBlockAction(button, stateValues)).toEqual({
179
- type: "a2ui:action",
180
- name: "send",
181
- surfaceId: "",
182
- sourceComponentId: "send",
183
- context: { name: "Grace" },
184
- });
185
- });
186
-
187
- it("uses the agent's fallback when Slack provides no state", () => {
188
- const { button } = convertForm();
189
-
190
- expect(decodeBlockAction(button)).toEqual({
191
- type: "a2ui:action",
192
- name: "send",
193
- surfaceId: "",
194
- sourceComponentId: "send",
195
- context: { name: "Ada" },
196
- });
197
- });
198
- });
@@ -1,194 +0,0 @@
1
- import type {
2
- SlackAlertBlock,
3
- SlackButtonElement,
4
- SlackCardBlock,
5
- SlackCarouselBlock,
6
- SlackContextBlock,
7
- SlackDataTableBlock,
8
- SlackDataTableCell,
9
- SlackImageBlock,
10
- SlackSectionBlock,
11
- SlackTextObject,
12
- } from "./types";
13
-
14
- /** The block limit for a Slack message. */
15
- export const MESSAGE_BLOCK_CAP = 50;
16
-
17
- /** The block limit for a Slack modal. */
18
- export const MODAL_BLOCK_CAP = 100;
19
-
20
- /**
21
- * The maximum number of top-level blocks accepted when parsing Slack Block
22
- * Kit JSON back into generative UI, guarding against a hostile or malformed
23
- * payload.
24
- */
25
- export const INBOUND_BLOCK_CAP = 200;
26
-
27
- /** The character limit for header text. */
28
- export const HEADER_TEXT_CAP = 150;
29
-
30
- /** The character limit for section text. */
31
- export const SECTION_TEXT_CAP = 3000;
32
-
33
- /** The maximum number of fields in one section. */
34
- export const FACT_FIELD_CAP = 10;
35
-
36
- /** The character limit for one section field. */
37
- export const FACT_FIELD_TEXT_CAP = 2000;
38
-
39
- /** The character limit for button labels and option labels. */
40
- export const INTERACTIVE_TEXT_CAP = 75;
41
-
42
- /** The character limit for an action_id. */
43
- export const ACTION_ID_CAP = 255;
44
-
45
- /** The character limit for a button's serialized value payload. */
46
- export const BUTTON_VALUE_CAP = 2000;
47
-
48
- /** The maximum number of elements in one actions block. */
49
- export const ACTIONS_ELEMENT_CAP = 25;
50
-
51
- /** The maximum number of options in a static select. */
52
- export const SELECT_OPTION_CAP = 100;
53
-
54
- /** The maximum number of options in a radio-button group. */
55
- export const RADIO_OPTION_CAP = 10;
56
-
57
- /** The maximum number of options in a checkbox group. */
58
- export const CHECKBOX_OPTION_CAP = 10;
59
-
60
- /** The character limit for a select or input placeholder. */
61
- export const PLACEHOLDER_TEXT_CAP = 150;
62
-
63
- /** The character limit for an input's label. */
64
- export const INPUT_LABEL_CAP = 2000;
65
-
66
- /** The maximum number of elements in one context block. */
67
- export const CONTEXT_ELEMENT_CAP = 10;
68
-
69
- /** The character limit for one context element's text. */
70
- export const CONTEXT_TEXT_CAP = 3000;
71
-
72
- /** The character limit for card titles and subtitles. */
73
- export const CARD_TITLE_CAP = 150;
74
-
75
- /** The character limit for a card's body text. */
76
- export const CARD_BODY_CAP = 200;
77
-
78
- /** The character limit for a card's subtext. */
79
- export const CARD_SUBTEXT_CAP = 200;
80
-
81
- /** The maximum number of buttons in a card's actions. */
82
- export const CARD_ACTIONS_CAP = 3;
83
-
84
- /** The minimum number of elements in a renderable carousel. */
85
- export const CAROUSEL_CARD_MIN = 1;
86
-
87
- /** The maximum number of elements in a carousel. */
88
- export const CAROUSEL_CARD_CAP = 10;
89
-
90
- /** The character limit for an alert's text. */
91
- export const ALERT_TEXT_CAP = 200;
92
-
93
- /**
94
- * The maximum number of data rows in a data-table block; the emitted `rows`
95
- * array additionally carries one header row.
96
- */
97
- export const DATA_TABLE_ROW_CAP = 200;
98
-
99
- /** The maximum number of columns in a data-table block. */
100
- export const DATA_TABLE_COLUMN_CAP = 20;
101
-
102
- /**
103
- * The cumulative character limit (summed across every cell's text) shared by
104
- * every data-table block in one payload.
105
- */
106
- export const DATA_TABLE_CHAR_BUDGET = 20000;
107
-
108
- /** The caption emitted on every native data-table block. */
109
- export const TABLE_CAPTION = "Table";
110
-
111
- /** The cumulative character limit for native markdown blocks. */
112
- export const MARKDOWN_TEXT_BUDGET = 12000;
113
-
114
- /** Builds the message-surface fallback for an alert. */
115
- export function buildAlertFallback(
116
- title: string,
117
- description: string,
118
- tone: "info" | "success" | "warning" | "danger",
119
- ): readonly [SlackContextBlock, SlackSectionBlock] {
120
- const emoji = {
121
- info: "ℹ️",
122
- success: "✅",
123
- warning: "⚠️",
124
- danger: "🛑",
125
- }[tone];
126
- return [
127
- {
128
- type: "context",
129
- elements: [
130
- { type: "mrkdwn", text: title ? `${emoji} *${title}*` : emoji },
131
- ],
132
- },
133
- { type: "section", text: { type: "mrkdwn", text: description } },
134
- ];
135
- }
136
-
137
- /** Builds a 2026 Slack modal-only alert block, mapping `tone` to its `level`. */
138
- export function buildAlertBlock(
139
- text: string,
140
- tone: "info" | "success" | "warning" | "danger",
141
- ): SlackAlertBlock {
142
- const level = tone === "danger" ? "error" : tone;
143
- return { type: "alert", text: { type: "mrkdwn", text }, level };
144
- }
145
-
146
- type CardBlockFields = {
147
- readonly heroImage?: SlackImageBlock;
148
- readonly title?: SlackTextObject;
149
- readonly subtitle?: SlackTextObject;
150
- readonly body?: SlackTextObject;
151
- readonly subtext?: SlackTextObject;
152
- readonly actions?: readonly SlackButtonElement[];
153
- };
154
-
155
- /** Builds a 2026 Slack card block from its already-clamped fields. */
156
- export function buildCardBlock(fields: CardBlockFields): SlackCardBlock {
157
- const { heroImage, title, subtitle, body, subtext, actions } = fields;
158
- return {
159
- type: "card",
160
- ...(heroImage !== undefined ? { hero_image: heroImage } : {}),
161
- ...(title !== undefined ? { title } : {}),
162
- ...(subtitle !== undefined ? { subtitle } : {}),
163
- ...(body !== undefined ? { body } : {}),
164
- ...(subtext !== undefined ? { subtext } : {}),
165
- ...(actions !== undefined ? { actions } : {}),
166
- };
167
- }
168
-
169
- /** Builds a 2026 Slack carousel block. */
170
- export function buildCarouselBlock(
171
- elements: readonly SlackCardBlock[],
172
- ): SlackCarouselBlock {
173
- return { type: "carousel", elements };
174
- }
175
-
176
- /** Builds a 2026 Slack data-table block; `rows[0]` must be the header row. */
177
- export function buildDataTableBlock(
178
- rows: readonly (readonly SlackDataTableCell[])[],
179
- ): SlackDataTableBlock {
180
- return { type: "data_table", caption: TABLE_CAPTION, rows };
181
- }
182
-
183
- /** Builds the final note used when a surface block budget is exceeded. */
184
- export function buildBlockBudgetNote(omitted: number): SlackContextBlock {
185
- return {
186
- type: "context",
187
- elements: [
188
- {
189
- type: "mrkdwn",
190
- text: `${omitted} ${omitted === 1 ? "block was" : "blocks were"} omitted.`,
191
- },
192
- ],
193
- };
194
- }