@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,33 +0,0 @@
1
- import { describe, expect, it } from "vitest";
2
- import { findAnsweredValues } from "./answeredValues";
3
-
4
- describe("findAnsweredValues", () => {
5
- it("returns the latest action's object input", () => {
6
- expect(
7
- findAnsweredValues({
8
- entries: [
9
- {
10
- type: "action",
11
- payload: { $input: { choice: "first" } },
12
- },
13
- { type: "human-response", payload: { choice: "ignored" } },
14
- {
15
- type: "action",
16
- payload: { $input: { choice: "last" } },
17
- },
18
- ],
19
- }),
20
- ).toEqual({ choice: "last" });
21
- });
22
-
23
- it("ignores actions whose input is not a plain object", () => {
24
- expect(
25
- findAnsweredValues({
26
- entries: [
27
- { type: "action", payload: { $input: { choice: "kept" } } },
28
- { type: "action", payload: { $input: ["not", "an", "object"] } },
29
- ],
30
- }),
31
- ).toEqual({ choice: "kept" });
32
- });
33
- });
@@ -1,70 +0,0 @@
1
- import * as React from "react";
2
-
3
- type AnsweredValues = Readonly<Record<string, unknown>>;
4
-
5
- type ToolInteractionLog = {
6
- readonly entries: readonly {
7
- readonly type: string;
8
- readonly payload: unknown;
9
- }[];
10
- };
11
-
12
- let answeredValuesContext:
13
- | React.Context<AnsweredValues | undefined>
14
- | undefined;
15
- const supportsContext = typeof React.createContext === "function";
16
- const useValuesContext = supportsContext ? React.useContext : () => undefined;
17
-
18
- const getAnsweredValuesContext = () =>
19
- supportsContext
20
- ? (answeredValuesContext ??= React.createContext<
21
- AnsweredValues | undefined
22
- >(undefined))
23
- : undefined;
24
-
25
- const isPlainObject = (value: unknown): value is AnsweredValues => {
26
- if (typeof value !== "object" || value === null || Array.isArray(value)) {
27
- return false;
28
- }
29
- const prototype = Object.getPrototypeOf(value);
30
- return prototype === Object.prototype || prototype === null;
31
- };
32
-
33
- export const findAnsweredValues = (
34
- interactions: ToolInteractionLog | undefined,
35
- ): AnsweredValues | undefined => {
36
- const entries = interactions?.entries;
37
- if (!entries) return undefined;
38
-
39
- for (let index = entries.length - 1; index >= 0; index--) {
40
- const interaction = entries[index]!;
41
- if (interaction.type !== "action" || !isPlainObject(interaction.payload)) {
42
- continue;
43
- }
44
- const input = interaction.payload["$input"];
45
- if (isPlainObject(input)) return input;
46
- }
47
- return undefined;
48
- };
49
-
50
- export const AnsweredValuesProvider = ({
51
- values,
52
- children,
53
- }: {
54
- values: AnsweredValues | undefined;
55
- children: React.ReactNode;
56
- }) => {
57
- const Context = getAnsweredValuesContext();
58
- return Context ? (
59
- <Context.Provider value={values}>{children}</Context.Provider>
60
- ) : (
61
- children
62
- );
63
- };
64
-
65
- export const useAnsweredValue = (name: string | undefined): unknown => {
66
- const values = useValuesContext(getAnsweredValuesContext()!);
67
- return name !== undefined && values && Object.hasOwn(values, name)
68
- ? values[name]
69
- : undefined;
70
- };
@@ -1,246 +0,0 @@
1
- import type { JSONSchema7, JSONSchema7Definition } from "json-schema";
2
- import { describe, expect, it, vi } from "vitest";
3
- import { z } from "zod";
4
- import { buildPresentParameters } from "./buildPresentParameters";
5
-
6
- const component = (properties: z.ZodType) => ({
7
- description: "A component",
8
- properties,
9
- render: () => null,
10
- });
11
-
12
- function resolve(root: JSONSchema7, value: JSONSchema7Definition) {
13
- let schema = value as JSONSchema7;
14
- if (schema.$ref === "#") return root;
15
- if (schema.$ref) {
16
- expect(schema.$ref).toMatch(/^#\//);
17
- let target: unknown = root;
18
- for (const segment of schema.$ref.slice(2).split("/")) {
19
- const key = decodeURIComponent(segment)
20
- .replace(/~1/g, "/")
21
- .replace(/~0/g, "~");
22
- target = (target as Record<string, unknown> | undefined)?.[key];
23
- }
24
- expect(target).toBeDefined();
25
- schema = target as JSONSchema7;
26
- }
27
- return schema;
28
- }
29
-
30
- describe("component schema references", () => {
31
- it("retains recursive definitions used by a component prop", () => {
32
- const Tree = z.object({
33
- label: z.string(),
34
- get descendants() {
35
- return z.array(Tree);
36
- },
37
- });
38
- const schema = buildPresentParameters({
39
- Tree: component(z.object({ tree: Tree })),
40
- });
41
- const tree = resolve(schema, schema.properties!.tree!);
42
- expect(tree.properties!.label).toEqual({ type: "string" });
43
- const descendants = tree.properties!.descendants as JSONSchema7;
44
- expect(resolve(schema, descendants.items as JSONSchema7Definition)).toBe(
45
- tree,
46
- );
47
- });
48
-
49
- it("keeps definitions with the same generated name isolated across components", () => {
50
- const TextTree = z.object({
51
- value: z.string(),
52
- get descendants() {
53
- return z.array(TextTree);
54
- },
55
- });
56
- const NumberTree = z.object({
57
- value: z.number(),
58
- get descendants() {
59
- return z.array(NumberTree);
60
- },
61
- });
62
- const schema = buildPresentParameters({
63
- node: component(z.object({ textTree: TextTree })),
64
- children: component(z.object({ numberTree: NumberTree })),
65
- });
66
- const textTree = resolve(schema, schema.properties!.textTree!);
67
- const numberTree = resolve(schema, schema.properties!.numberTree!);
68
- expect(textTree.properties!.value).toEqual({ type: "string" });
69
- expect(numberTree.properties!.value).toEqual({ type: "number" });
70
- expect(
71
- resolve(
72
- schema,
73
- (textTree.properties!.descendants as JSONSchema7)
74
- .items as JSONSchema7Definition,
75
- ),
76
- ).toBe(textTree);
77
- expect(
78
- resolve(
79
- schema,
80
- (numberTree.properties!.descendants as JSONSchema7)
81
- .items as JSONSchema7Definition,
82
- ),
83
- ).toBe(numberTree);
84
- expect(resolve(schema, schema.properties!.children!).anyOf).toBeDefined();
85
- expect((schema.$defs!.node as JSONSchema7).properties!.$type).toBeDefined();
86
- });
87
-
88
- it("rebases root references to component props instead of the generated UI node", () => {
89
- const Props = z.object({
90
- label: z.string(),
91
- get nested() {
92
- return z.array(Props);
93
- },
94
- });
95
- const schema = buildPresentParameters({ Recursive: component(Props) });
96
- const nested = schema.properties!.nested as JSONSchema7;
97
- const props = resolve(schema, nested.items as JSONSchema7Definition);
98
- expect(props.properties!.label).toEqual({ type: "string" });
99
- expect(props.properties!.$type).toBeUndefined();
100
- expect(props.required).toEqual(["label", "nested"]);
101
- });
102
-
103
- it("rebases recursive references inside unions and records", () => {
104
- const Tree = z.object({
105
- get branches() {
106
- return z.record(z.string(), z.union([z.string(), Tree]));
107
- },
108
- });
109
- const schema = buildPresentParameters({
110
- Tree: component(z.object({ tree: Tree })),
111
- });
112
- const tree = resolve(schema, schema.properties!.tree!);
113
- const branches = tree.properties!.branches as JSONSchema7;
114
- const variants = (branches.additionalProperties as JSONSchema7).anyOf!;
115
- expect(resolve(schema, variants[1]!)).toBe(tree);
116
- });
117
-
118
- it("retains recursive definitions referenced from tuple items", () => {
119
- const Tree = z.object({
120
- label: z.string(),
121
- get descendants() {
122
- return z.array(Tree);
123
- },
124
- });
125
- const schema = buildPresentParameters({
126
- Pair: component(z.object({ pair: z.tuple([Tree, z.string()]) })),
127
- });
128
- const pair = schema.properties!.pair as JSONSchema7 & {
129
- prefixItems?: JSONSchema7Definition[];
130
- };
131
- const first = (pair.prefixItems ??
132
- (pair.items as JSONSchema7Definition[]))[0]!;
133
- const tree = resolve(schema, first);
134
- expect(tree.properties!.label).toEqual({ type: "string" });
135
- expect(
136
- resolve(
137
- schema,
138
- (tree.properties!.descendants as JSONSchema7)
139
- .items as JSONSchema7Definition,
140
- ),
141
- ).toBe(tree);
142
- });
143
-
144
- it("rebases prefixItems in draft 2020-12 converter output", () => {
145
- const raw = {
146
- $schema: "https://json-schema.org/draft/2020-12/schema",
147
- $id: "https://example.com/pair",
148
- type: "object",
149
- properties: {
150
- pair: {
151
- type: "array",
152
- prefixItems: [{ $ref: "#/$defs/item" }, { type: "string" }],
153
- },
154
- },
155
- $defs: {
156
- item: {
157
- type: "object",
158
- properties: {
159
- descendants: { type: "array", items: { $ref: "#/$defs/item" } },
160
- },
161
- },
162
- },
163
- };
164
- const schema = buildPresentParameters({
165
- Pair: component(raw as unknown as z.ZodType),
166
- });
167
- const pair = schema.properties!.pair as JSONSchema7 & {
168
- prefixItems: JSONSchema7Definition[];
169
- };
170
- const item = resolve(schema, pair.prefixItems[0]!);
171
- expect(item.properties!.descendants).toBeDefined();
172
- expect(
173
- resolve(
174
- schema,
175
- (item.properties!.descendants as JSONSchema7)
176
- .items as JSONSchema7Definition,
177
- ),
178
- ).toBe(item);
179
- const embedded = schema.$defs!.component0 as JSONSchema7;
180
- expect(Object.keys(embedded)).not.toContain("$schema");
181
- expect(Object.keys(embedded)).not.toContain("$id");
182
- });
183
-
184
- it("strips root resource metadata from embedded component schemas", () => {
185
- const Tree = z.object({
186
- get descendants() {
187
- return z.array(Tree);
188
- },
189
- });
190
- const schema = buildPresentParameters({
191
- Tree: component(z.object({ tree: Tree })),
192
- });
193
- const embedded = schema.$defs!.component0 as JSONSchema7;
194
- expect(Object.keys(embedded)).not.toContain("$schema");
195
- expect(embedded.type).toBe("object");
196
- });
197
-
198
- it("omits component definitions whose properties all lose the duplicate-name merge", () => {
199
- const warn = vi.spyOn(console, "warn").mockImplementation(() => {});
200
- try {
201
- const StringTree = z.object({
202
- get tree() {
203
- return z.array(StringTree);
204
- },
205
- });
206
- const NumberTree = z.object({
207
- value: z.number(),
208
- get tree() {
209
- return z.array(NumberTree);
210
- },
211
- });
212
- const schema = buildPresentParameters({
213
- first: component(z.object({ tree: StringTree })),
214
- second: component(z.object({ tree: NumberTree })),
215
- });
216
- expect(Object.keys(schema.$defs!)).toEqual([
217
- "node",
218
- "children",
219
- "component0",
220
- ]);
221
- expect(warn).toHaveBeenCalledOnce();
222
- } finally {
223
- warn.mockRestore();
224
- }
225
- });
226
-
227
- it("leaves literal reference-shaped data and non-recursive schemas unchanged", () => {
228
- const literal = {
229
- $ref: "#/literal",
230
- properties: { nested: { $ref: "#" } },
231
- };
232
- const schema = buildPresentParameters({
233
- Card: component(
234
- z.object({
235
- label: z.string().meta({ examples: [literal], default: literal }),
236
- }),
237
- ),
238
- });
239
- expect(schema.properties!.label).toEqual({
240
- type: "string",
241
- examples: [literal],
242
- default: literal,
243
- });
244
- expect(Object.keys(schema.$defs!)).toEqual(["node", "children"]);
245
- });
246
- });
@@ -1,130 +0,0 @@
1
- import { toJSONSchema } from "assistant-stream";
2
- import type { JSONSchema7, JSONSchema7Definition } from "json-schema";
3
- import { TYPE_KEY } from "./constants";
4
- import type { GenerativeUILibrary } from "./types";
5
- import { scopeSchema } from "./scopeSchema";
6
-
7
- /**
8
- * Builds the JSON schema for the `present` tool from a {@link GenerativeUILibrary}.
9
- *
10
- * The model produces a node `{ $type, $key?, ...props }` where `$type` selects
11
- * a component, the optional `$key` pins a stable identity for list items that
12
- * may reorder, and the rest are its props. The schema is a flat object: `$type`
13
- * is an enum of the component names, every component's props are merged into
14
- * one optional bag, and `children` recurses via `$defs` so the tree can nest.
15
- *
16
- * It is intentionally flat rather than a per-`$type` discriminated union. Tool /
17
- * function-call schemas (OpenAI and others) require the top-level parameters to
18
- * be a plain object and reject a top-level `oneOf`/`anyOf`/`enum`. So props can't
19
- * be refined per `$type` at the root; the model is guided instead by `$type`'s
20
- * description (which lists each component) and each prop's own description. The
21
- * renderer validates nothing here — an unknown `$type` or stray prop is handled
22
- * at render time — so a looser schema only costs the model a hint, not safety.
23
- */
24
- export function buildPresentParameters(
25
- library: GenerativeUILibrary,
26
- ): JSONSchema7 {
27
- const names = Object.keys(library);
28
-
29
- // Merge every component's props into one optional bag. `$`-prefixed keys and
30
- // `children` are framework-reserved (see ir.ts), so drop any author-declared
31
- // copies. On a name clash the first component's schema wins — props are an
32
- // advisory hint here, not a strict per-component contract.
33
- const props = new Map<string, JSONSchema7Definition>();
34
- const propOwners = new Map<string, string[]>();
35
- const componentSchemas: Record<string, JSONSchema7> = {};
36
- for (const [index, name] of names.entries()) {
37
- const definition = `component${index}`;
38
- const { schema: propsSchema, referenced } = scopeSchema(
39
- toJSONSchema(library[name]!.properties),
40
- `#/$defs/${definition}`,
41
- );
42
- if (propsSchema.type !== "object") {
43
- throw new Error(
44
- `[@assistant-ui/react-generative-ui] Component "${name}": ` +
45
- "`properties` must be an object schema (e.g. `z.object({ ... })`).",
46
- );
47
- }
48
- let merged = false;
49
- for (const [key, schema] of Object.entries(propsSchema.properties ?? {})) {
50
- if (key.startsWith("$") || key === "children") continue;
51
- // secure-json-parse rejects the whole tool-argument payload on this key,
52
- // so advertising it would cost the model the node rather than one prop.
53
- if (key === "__proto__") continue;
54
- if (!props.has(key)) {
55
- props.set(key, schema);
56
- merged = true;
57
- }
58
- propOwners.set(key, [...(propOwners.get(key) ?? []), name]);
59
- }
60
- if (referenced && merged) {
61
- // `$schema` is only valid at a schema-resource root, and an embedded
62
- // `$id` would both collide across components and re-base the pointers
63
- // scopeSchema just rewrote to be document-root relative.
64
- const { $schema: _, $id: _id, ...embedded } = propsSchema;
65
- componentSchemas[definition] = embedded;
66
- }
67
- }
68
-
69
- if (process.env["NODE_ENV"] !== "production") {
70
- for (const [key, owners] of propOwners) {
71
- if (owners.length < 2) continue;
72
- // eslint-disable-next-line no-console
73
- console.warn(
74
- `[@assistant-ui/react-generative-ui] Prop "${key}" is declared by ` +
75
- `${formatComponentList(owners)}; keeping "${owners[0]}"'s schema. ` +
76
- "Rename or align the prop type to avoid an ambiguous schema.",
77
- );
78
- }
79
- }
80
-
81
- // Carry each component's description on the `$type` enum, since there are no
82
- // per-branch schemas to hang them on anymore.
83
- const typeDescription =
84
- names.length > 0
85
- ? `The component to render. ${names
86
- .map((name) => `"${name}": ${library[name]!.description}`)
87
- .join("; ")}`
88
- : "The component to render.";
89
-
90
- const node: JSONSchema7 = {
91
- type: "object",
92
- properties: {
93
- [TYPE_KEY]: { type: "string", enum: names, description: typeDescription },
94
- $key: {
95
- description:
96
- "Stable identity for this UI node. Use it for list items that may reorder.",
97
- anyOf: [{ type: "string" }, { type: "number" }],
98
- },
99
- ...Object.fromEntries(props),
100
- children: { $ref: "#/$defs/children" },
101
- },
102
- required: [TYPE_KEY],
103
- };
104
-
105
- const children: JSONSchema7 = {
106
- description: "Nested generative UI rendered inside this component.",
107
- anyOf: [
108
- { type: "string" },
109
- { $ref: "#/$defs/node" },
110
- {
111
- type: "array",
112
- items: { anyOf: [{ type: "string" }, { $ref: "#/$defs/node" }] },
113
- },
114
- ],
115
- };
116
-
117
- return {
118
- ...node,
119
- $defs: { node, children, ...componentSchemas },
120
- };
121
- }
122
-
123
- function formatComponentList(names: string[]) {
124
- if (names.length <= 2) return names.map((name) => `"${name}"`).join(" and ");
125
-
126
- return `${names
127
- .slice(0, -1)
128
- .map((name) => `"${name}"`)
129
- .join(", ")}, and "${names[names.length - 1]}"`;
130
- }
package/src/constants.ts DELETED
@@ -1,17 +0,0 @@
1
- /**
2
- * The wire key naming the component to render.
3
- *
4
- * The model emits nodes as flat objects `{ [TYPE_KEY]: name, ...props }`. We
5
- * use `$type` rather than `type` so a component's own `type` prop (e.g. a
6
- * button's `type="submit"`) flows through as an ordinary prop without
7
- * colliding with the discriminator. It mirrors the `$type` discriminator
8
- * convention used by JSON polymorphism elsewhere.
9
- */
10
- export const TYPE_KEY = "$type";
11
-
12
- export const GENERATED_NAME_ATTR = "data-aui-generated-name";
13
-
14
- export const FIELD_NAME_ATTR = "data-aui-field-name";
15
- export const FIELD_VALUE_ATTR = "data-aui-field-value";
16
-
17
- export const CHECKBOX_GROUP_ATTR = "data-aui-checkbox-group";
@@ -1,192 +0,0 @@
1
- import { describe, expect, it } from "vitest";
2
- import {
3
- CHILDREN_CAP,
4
- MAX_ELEMENT_DEPTH,
5
- NODE_BUDGET,
6
- boundSpec,
7
- clampReasonDetail,
8
- type ClampReason,
9
- } from "./boundSpec";
10
-
11
- const walk = (spec: unknown) => {
12
- const reasons: ClampReason[] = [];
13
- const result = boundSpec(spec, (reason) => reasons.push(reason));
14
- return { reasons, result };
15
- };
16
-
17
- const nest = (levels: number): unknown =>
18
- levels === 0
19
- ? { type: "Text" }
20
- : { type: "Box", children: [nest(levels - 1)] };
21
-
22
- describe("boundSpec", () => {
23
- it("copies an object without children and passes primitives through", () => {
24
- const leaf = { type: "Text", props: { value: "hi" } };
25
- const { reasons, result } = walk(leaf);
26
-
27
- expect({ reasons, result }).toEqual({ reasons: [], result: leaf });
28
- expect(result).not.toBe(leaf);
29
- expect(walk("text").result).toBe("text");
30
- expect(walk(null).result).toBe(null);
31
- });
32
-
33
- it("copies a node with children instead of mutating the caller's object", () => {
34
- const spec = { type: "Box", children: [] };
35
- const { result } = walk(spec);
36
-
37
- expect(result).toEqual(spec);
38
- expect(result).not.toBe(spec);
39
- });
40
-
41
- it("clamps any level wider than the children cap", () => {
42
- const { reasons, result } = walk(Array(CHILDREN_CAP + 1).fill("x"));
43
-
44
- expect(reasons).toEqual(["children"]);
45
- expect(result as unknown[]).toHaveLength(CHILDREN_CAP);
46
- });
47
-
48
- it("bounds an array whose reported length is fabricated", () => {
49
- const hostile = new Proxy(["a", "b"], {
50
- get: (target, prop, receiver) =>
51
- prop === "length"
52
- ? Number.MAX_SAFE_INTEGER
53
- : Reflect.get(target, prop, receiver),
54
- });
55
-
56
- const { reasons, result } = walk(hostile);
57
-
58
- expect(reasons).toEqual(["children"]);
59
- expect(result as unknown[]).toHaveLength(CHILDREN_CAP);
60
- });
61
-
62
- it("bounds an array that hijacks its own slice", () => {
63
- const hostile = new Proxy(["a", "b"], {
64
- get: (target, prop, receiver) =>
65
- prop === "slice"
66
- ? () => Array(CHILDREN_CAP * 2).fill("x")
67
- : Reflect.get(target, prop, receiver),
68
- });
69
-
70
- expect(walk(hostile).result).toEqual(["a", "b"]);
71
- });
72
-
73
- it("bounds an array that redirects Symbol.species to a foreign object", () => {
74
- function HostileCtor() {
75
- return { map: () => Array(500).fill({ type: "Text" }), length: 0 };
76
- }
77
- const constructor = { [Symbol.species]: HostileCtor };
78
- const hostile = new Proxy([{ type: "Text" }, { type: "Text" }], {
79
- get: (target, prop, receiver) =>
80
- prop === "constructor"
81
- ? constructor
82
- : Reflect.get(target, prop, receiver),
83
- });
84
-
85
- const { reasons, result } = walk(hostile);
86
-
87
- expect(reasons).toEqual([]);
88
- expect(result).toEqual([{ type: "Text" }, { type: "Text" }]);
89
- });
90
-
91
- it("bounds a record that hides its children behind a has trap", () => {
92
- const hostileChildren = new Proxy([{ type: "Text" }], {
93
- get: (target, prop, receiver) =>
94
- prop === "length"
95
- ? Number.MAX_SAFE_INTEGER
96
- : Reflect.get(target, prop, receiver),
97
- });
98
- const hostile = new Proxy(
99
- { type: "Box", children: hostileChildren } as Record<string, unknown>,
100
- {
101
- has: (target, prop) => prop !== "children" && Reflect.has(target, prop),
102
- },
103
- );
104
-
105
- const { reasons, result } = walk(hostile);
106
-
107
- expect(reasons).toEqual(["children"]);
108
- expect((result as { children: unknown[] }).children).toHaveLength(
109
- CHILDREN_CAP,
110
- );
111
- });
112
-
113
- it("bounds a record that answers a later read of children differently", () => {
114
- let reads = 0;
115
- const hostile = new Proxy(
116
- { type: "Box", children: undefined } as Record<string, unknown>,
117
- {
118
- get: (target, prop, receiver) => {
119
- if (prop !== "children") return Reflect.get(target, prop, receiver);
120
- reads += 1;
121
- return reads === 1
122
- ? undefined
123
- : Array(CHILDREN_CAP * 2).fill({ type: "Text" });
124
- },
125
- },
126
- );
127
-
128
- const { reasons, result } = walk(hostile);
129
-
130
- expect(reasons).toEqual([]);
131
- expect((result as { children: unknown }).children).toBeUndefined();
132
- });
133
-
134
- it("accepts nesting exactly at the element-depth ceiling", () => {
135
- expect(walk(nest(MAX_ELEMENT_DEPTH)).reasons).toEqual([]);
136
- });
137
-
138
- it("drops nodes one level past the element-depth ceiling", () => {
139
- const { reasons, result } = walk(nest(MAX_ELEMENT_DEPTH + 1));
140
-
141
- expect(reasons).toEqual(["depth"]);
142
- expect(JSON.stringify(result)).toContain('"children":null');
143
- });
144
-
145
- it("spends a single budget across shared references and reports it once", () => {
146
- let spec: unknown = { type: "Text" };
147
- for (let level = 0; level < 20; level += 1) {
148
- spec = { type: "Box", children: [spec, spec] };
149
- }
150
-
151
- expect(walk(spec).reasons).toEqual(["budget"]);
152
- });
153
-
154
- it("cuts a node that is its own ancestor", () => {
155
- const cyclic: Record<string, unknown> = { type: "Box" };
156
- cyclic["children"] = [cyclic];
157
-
158
- const { reasons, result } = walk(cyclic);
159
-
160
- expect(reasons).toEqual(["cycle"]);
161
- expect(result).toEqual({ type: "Box", children: [null] });
162
- });
163
-
164
- it("keeps a repeated sibling that is not an ancestor", () => {
165
- const shared = { type: "Box", children: [] };
166
-
167
- const { reasons, result } = walk({
168
- type: "Row",
169
- children: [shared, shared],
170
- });
171
-
172
- expect(reasons).toEqual([]);
173
- expect(result).toEqual({ type: "Row", children: [shared, shared] });
174
- });
175
- });
176
-
177
- describe("clampReasonDetail", () => {
178
- it("renders one detail per reason", () => {
179
- expect(clampReasonDetail("budget")).toBe(
180
- `the tree was truncated after ${NODE_BUDGET} nodes.`,
181
- );
182
- expect(clampReasonDetail("cycle")).toBe(
183
- "a self-referencing node was dropped.",
184
- );
185
- expect(clampReasonDetail("depth")).toBe(
186
- `nodes deeper than ${MAX_ELEMENT_DEPTH} levels were dropped.`,
187
- );
188
- expect(clampReasonDetail("children")).toBe(
189
- `children were clamped to ${CHILDREN_CAP} entries.`,
190
- );
191
- });
192
- });