@assistant-ui/react-generative-ui 0.0.23 → 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 +5 -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 -152
  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 -530
  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 -319
  306. package/src/temporal.ts +0 -259
  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 -1148
  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 -1048
  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,523 +0,0 @@
1
- const MAX_TEMPLATE_LENGTH = 10_000;
2
- const MAX_TEMPLATE_PARTS = 1_000;
3
- const MAX_EXPRESSION_DEPTH = 32;
4
-
5
- export type ExpressionPart =
6
- | string
7
- | number
8
- | boolean
9
- | null
10
- | { readonly path: string }
11
- | { readonly call: string; readonly args: Record<string, ExpressionPart> };
12
-
13
- export type ValueFunctionContext = {
14
- readonly resolve: (value: ExpressionPart) => unknown;
15
- readonly warn: (message: string) => void;
16
- readonly templates: Map<string, ExpressionPart[] | null>;
17
- };
18
-
19
- export class ExpressionSyntaxError extends Error {}
20
-
21
- const FUNCTION_NAME = /^[A-Za-z_@]\w*$/;
22
-
23
- const isSpace = (character: string) => /\s/.test(character);
24
-
25
- const isPathCharacter = (character: string) =>
26
- !isSpace(character) && !"{}(),:'\"$".includes(character);
27
-
28
- const isDigit = (character: string | undefined) =>
29
- character !== undefined && character >= "0" && character <= "9";
30
-
31
- class ExpressionReader {
32
- private readonly text: string;
33
- position: number;
34
-
35
- constructor(text: string, position: number) {
36
- this.text = text;
37
- this.position = position;
38
- }
39
-
40
- expression(depth: number): ExpressionPart {
41
- if (depth > MAX_EXPRESSION_DEPTH) this.fail("it nests too deeply");
42
- this.skipSpace();
43
- const character = this.text[this.position];
44
- if (this.text.startsWith("${", this.position)) {
45
- this.position += 2;
46
- this.skipSpace();
47
- if (this.text[this.position] === "}") {
48
- this.position++;
49
- return "";
50
- }
51
- const value = this.expression(depth + 1);
52
- this.skipSpace();
53
- if (this.text[this.position] !== "}") {
54
- this.fail(
55
- this.position >= this.text.length
56
- ? "an interpolation is not closed"
57
- : "characters follow a complete expression",
58
- );
59
- }
60
- this.position++;
61
- return value;
62
- }
63
- if (character === "'" || character === '"') return this.string(character);
64
- if (
65
- isDigit(character) ||
66
- (character === "-" && isDigit(this.text[this.position + 1]))
67
- ) {
68
- return this.number();
69
- }
70
- const word = this.word();
71
- if (word === "") this.fail("an expression is missing");
72
- const end = this.position;
73
- this.skipSpace();
74
- if (this.text[this.position] === "(") {
75
- if (!FUNCTION_NAME.test(word)) this.fail(`"${word}" is not a function`);
76
- this.position++;
77
- return { call: word, args: this.arguments(depth) };
78
- }
79
- this.position = end;
80
- if (word === "true") return true;
81
- if (word === "false") return false;
82
- if (word === "null") return null;
83
- return { path: word };
84
- }
85
-
86
- private arguments(depth: number): Record<string, ExpressionPart> {
87
- const args: Record<string, ExpressionPart> = {};
88
- this.skipSpace();
89
- if (this.text[this.position] === ")") {
90
- this.position++;
91
- return args;
92
- }
93
- for (;;) {
94
- this.skipSpace();
95
- const name = this.word();
96
- if (!FUNCTION_NAME.test(name)) this.fail("an argument name is missing");
97
- this.skipSpace();
98
- if (this.text[this.position] !== ":") {
99
- this.fail(`argument "${name}" has no colon`);
100
- }
101
- this.position++;
102
- Object.defineProperty(args, name, {
103
- value: this.expression(depth + 1),
104
- enumerable: true,
105
- configurable: true,
106
- writable: true,
107
- });
108
- this.skipSpace();
109
- const separator = this.text[this.position++];
110
- if (separator === ")") return args;
111
- if (separator !== ",") {
112
- this.fail(
113
- separator === undefined
114
- ? "a function call is not closed"
115
- : "arguments are not separated by commas",
116
- );
117
- }
118
- this.skipSpace();
119
- if (this.text[this.position] === ")") {
120
- this.position++;
121
- return args;
122
- }
123
- }
124
- }
125
-
126
- private string(quote: string): string {
127
- let value = "";
128
- this.position++;
129
- for (;;) {
130
- const character = this.text[this.position++];
131
- if (character === undefined) this.fail("a string is not closed");
132
- if (character === quote) return value;
133
- if (character !== "\\") {
134
- value += character;
135
- continue;
136
- }
137
- const escaped = this.text[this.position++];
138
- if (escaped === undefined) this.fail("a string is not closed");
139
- value +=
140
- escaped === "n"
141
- ? "\n"
142
- : escaped === "t"
143
- ? "\t"
144
- : escaped === "r"
145
- ? "\r"
146
- : escaped;
147
- }
148
- }
149
-
150
- private number(): number {
151
- const start = this.position;
152
- if (this.text[this.position] === "-") this.position++;
153
- while (isDigit(this.text[this.position])) this.position++;
154
- if (this.text[this.position] === ".") {
155
- this.position++;
156
- while (isDigit(this.text[this.position])) this.position++;
157
- }
158
- const next = this.text[this.position];
159
- if (next !== undefined && isPathCharacter(next)) {
160
- this.fail("a number is malformed");
161
- }
162
- return Number(this.text.slice(start, this.position));
163
- }
164
-
165
- private word(): string {
166
- const start = this.position;
167
- while (
168
- this.position < this.text.length &&
169
- isPathCharacter(this.text[this.position]!)
170
- ) {
171
- this.position++;
172
- }
173
- return this.text.slice(start, this.position);
174
- }
175
-
176
- private skipSpace() {
177
- while (
178
- this.position < this.text.length &&
179
- isSpace(this.text[this.position]!)
180
- ) {
181
- this.position++;
182
- }
183
- }
184
-
185
- private fail(reason: string): never {
186
- throw new ExpressionSyntaxError(reason);
187
- }
188
- }
189
-
190
- export const parseExpressionTemplate = (template: string): ExpressionPart[] => {
191
- if (template.length > MAX_TEMPLATE_LENGTH) {
192
- throw new ExpressionSyntaxError(
193
- `it is longer than ${MAX_TEMPLATE_LENGTH} characters`,
194
- );
195
- }
196
- const parts: ExpressionPart[] = [];
197
- const push = (part: ExpressionPart) => {
198
- if (parts.length >= MAX_TEMPLATE_PARTS) {
199
- throw new ExpressionSyntaxError(
200
- `it has more than ${MAX_TEMPLATE_PARTS} parts`,
201
- );
202
- }
203
- parts.push(part);
204
- };
205
- let literal = "";
206
- let index = 0;
207
- while (index < template.length) {
208
- if (template.startsWith("\\${", index)) {
209
- literal += "${";
210
- index += 3;
211
- } else if (template.startsWith("${", index)) {
212
- if (literal) push(literal);
213
- literal = "";
214
- const reader = new ExpressionReader(template, index);
215
- push(reader.expression(0));
216
- index = reader.position;
217
- } else {
218
- literal += template[index];
219
- index++;
220
- }
221
- }
222
- if (literal) push(literal);
223
- return parts;
224
- };
225
-
226
- const isMissing = (value: unknown) =>
227
- value === undefined || value === null || value === "";
228
-
229
- const toNumber = (value: unknown): number | undefined => {
230
- const number =
231
- typeof value === "number"
232
- ? value
233
- : typeof value === "string" && value.trim() !== ""
234
- ? Number(value)
235
- : Number.NaN;
236
- return Number.isFinite(number) ? number : undefined;
237
- };
238
-
239
- const DATE_ONLY = /^(\d{4})-(\d{2})-(\d{2})$/;
240
-
241
- const toDate = (value: unknown): Date | undefined => {
242
- if (typeof value === "number") return new Date(value);
243
- if (typeof value !== "string") return undefined;
244
- const dateOnly = DATE_ONLY.exec(value);
245
- if (!dateOnly) return new Date(value);
246
- const [year, month, day] = dateOnly.slice(1).map(Number) as [
247
- number,
248
- number,
249
- number,
250
- ];
251
- const date = new Date(0);
252
- date.setFullYear(year, month - 1, day);
253
- date.setHours(0, 0, 0, 0);
254
- return date.getMonth() === month - 1 && date.getDate() === day
255
- ? date
256
- : undefined;
257
- };
258
-
259
- const toText = (value: unknown): string => {
260
- if (value === undefined || value === null) return "";
261
- if (typeof value !== "object") return String(value);
262
- try {
263
- return JSON.stringify(value) ?? "";
264
- } catch {
265
- return "";
266
- }
267
- };
268
-
269
- const cached = <T>(cache: Map<string, T>, key: string, create: () => T): T => {
270
- let value = cache.get(key);
271
- if (value === undefined) {
272
- value = create();
273
- if (cache.size >= 64) cache.clear();
274
- cache.set(key, value);
275
- }
276
- return value;
277
- };
278
-
279
- const numberFormats = new Map<string, Intl.NumberFormat>();
280
- const dateFormats = new Map<string, Intl.DateTimeFormat>();
281
- const pluralRules = new Map<string, Intl.PluralRules>();
282
-
283
- const invalidArguments = (name: string, context: ValueFunctionContext) => {
284
- context.warn(`A2UI function "${name}" received invalid arguments.`);
285
- return undefined;
286
- };
287
-
288
- const formatAmount = (
289
- name: string,
290
- args: Record<string, unknown>,
291
- context: ValueFunctionContext,
292
- currency?: string,
293
- ): unknown => {
294
- if (isMissing(args["value"])) return "";
295
- const value = toNumber(args["value"]);
296
- const decimals =
297
- args["decimals"] === undefined ? undefined : toNumber(args["decimals"]);
298
- if (
299
- value === undefined ||
300
- (args["decimals"] !== undefined &&
301
- (decimals === undefined ||
302
- !Number.isInteger(decimals) ||
303
- decimals < 0 ||
304
- decimals > 20))
305
- ) {
306
- return invalidArguments(name, context);
307
- }
308
- const options: Intl.NumberFormatOptions = {
309
- useGrouping: args["grouping"] !== false,
310
- ...(currency !== undefined ? { style: "currency", currency } : {}),
311
- ...(decimals !== undefined
312
- ? { minimumFractionDigits: decimals, maximumFractionDigits: decimals }
313
- : {}),
314
- };
315
- try {
316
- return cached(
317
- numberFormats,
318
- JSON.stringify(options),
319
- () => new Intl.NumberFormat(undefined, options),
320
- ).format(value);
321
- } catch {
322
- return invalidArguments(name, context);
323
- }
324
- };
325
-
326
- const dateName = (
327
- date: Date,
328
- options: Intl.DateTimeFormatOptions,
329
- type: Intl.DateTimeFormatPartTypes,
330
- ): string | undefined => {
331
- try {
332
- return cached(
333
- dateFormats,
334
- JSON.stringify(options),
335
- () => new Intl.DateTimeFormat(undefined, options),
336
- )
337
- .formatToParts(date)
338
- .find((part) => part.type === type)?.value;
339
- } catch {
340
- return undefined;
341
- }
342
- };
343
-
344
- const pad = (value: number, width: number) =>
345
- String(value).padStart(width, "0");
346
-
347
- const dateField = (
348
- date: Date,
349
- letter: string,
350
- width: number,
351
- ): string | undefined => {
352
- switch (letter) {
353
- case "y":
354
- return width === 2
355
- ? pad(date.getFullYear() % 100, 2)
356
- : pad(date.getFullYear(), width);
357
- case "M":
358
- return width <= 2
359
- ? pad(date.getMonth() + 1, width)
360
- : dateName(
361
- date,
362
- { month: width === 3 ? "short" : width === 4 ? "long" : "narrow" },
363
- "month",
364
- );
365
- case "d":
366
- return pad(date.getDate(), width);
367
- case "E":
368
- return dateName(
369
- date,
370
- { weekday: width <= 3 ? "short" : width === 4 ? "long" : "narrow" },
371
- "weekday",
372
- );
373
- case "h":
374
- return pad(date.getHours() % 12 || 12, width);
375
- case "H":
376
- return pad(date.getHours(), width);
377
- case "m":
378
- return pad(date.getMinutes(), width);
379
- case "s":
380
- return pad(date.getSeconds(), width);
381
- case "a":
382
- return (
383
- dateName(date, { hour: "numeric", hour12: true }, "dayPeriod") ??
384
- (date.getHours() < 12 ? "AM" : "PM")
385
- );
386
- default:
387
- return undefined;
388
- }
389
- };
390
-
391
- const formatDatePattern = (date: Date, pattern: string): string | undefined => {
392
- let result = "";
393
- let index = 0;
394
- while (index < pattern.length) {
395
- const character = pattern[index]!;
396
- if (character === "'") {
397
- index++;
398
- if (pattern[index] === "'") {
399
- result += "'";
400
- index++;
401
- continue;
402
- }
403
- while (index < pattern.length) {
404
- if (pattern[index] !== "'") {
405
- result += pattern[index++];
406
- } else if (pattern[index + 1] === "'") {
407
- result += "'";
408
- index += 2;
409
- } else {
410
- index++;
411
- break;
412
- }
413
- }
414
- } else if (/[A-Za-z]/.test(character)) {
415
- let end = index + 1;
416
- while (pattern[end] === character) end++;
417
- const field = dateField(date, character, end - index);
418
- if (field === undefined) return undefined;
419
- result += field;
420
- index = end;
421
- } else {
422
- result += character;
423
- index++;
424
- }
425
- }
426
- return result;
427
- };
428
-
429
- const pluralCategory = (value: number): string => {
430
- try {
431
- return cached(pluralRules, "", () => new Intl.PluralRules()).select(value);
432
- } catch {
433
- return value === 1 ? "one" : "other";
434
- }
435
- };
436
-
437
- type ValueFunction = (
438
- args: Record<string, unknown>,
439
- context: ValueFunctionContext,
440
- ) => unknown;
441
-
442
- const VALUE_FUNCTIONS: Readonly<Record<string, ValueFunction>> = {
443
- formatString: (args, context) => {
444
- const template = args["value"];
445
- if (template === undefined || template === null) return "";
446
- if (typeof template !== "string") {
447
- return invalidArguments("formatString", context);
448
- }
449
- let parts = context.templates.get(template);
450
- if (parts === undefined) {
451
- try {
452
- parts = parseExpressionTemplate(template);
453
- } catch (error) {
454
- if (!(error instanceof ExpressionSyntaxError)) throw error;
455
- context.warn(
456
- `A2UI formatString template is malformed: ${error.message}.`,
457
- );
458
- parts = null;
459
- }
460
- context.templates.set(template, parts);
461
- }
462
- if (parts === null) return undefined;
463
- return parts
464
- .map((part) =>
465
- toText(
466
- typeof part === "object" && part !== null
467
- ? context.resolve(part)
468
- : part,
469
- ),
470
- )
471
- .join("");
472
- },
473
- formatNumber: (args, context) => formatAmount("formatNumber", args, context),
474
- formatCurrency: (args, context) =>
475
- typeof args["currency"] === "string"
476
- ? formatAmount("formatCurrency", args, context, args["currency"])
477
- : invalidArguments("formatCurrency", context),
478
- formatDate: (args, context) => {
479
- const value = args["value"];
480
- if (isMissing(value)) return "";
481
- const format = args["format"];
482
- const date = toDate(value);
483
- if (typeof format !== "string" || !date || Number.isNaN(date.getTime())) {
484
- return invalidArguments("formatDate", context);
485
- }
486
- const formatted = formatDatePattern(date, format);
487
- if (formatted !== undefined) return formatted;
488
- context.warn(
489
- `A2UI function "formatDate" does not support the pattern "${format}", so it returned an ISO date.`,
490
- );
491
- return date.toISOString();
492
- },
493
- pluralize: (args, context) => {
494
- if (isMissing(args["value"])) return "";
495
- const value = toNumber(args["value"]);
496
- if (value === undefined || typeof args["other"] !== "string") {
497
- return invalidArguments("pluralize", context);
498
- }
499
- const selected = args[pluralCategory(value)];
500
- return typeof selected === "string" ? selected : args["other"];
501
- },
502
- and: (args, context) =>
503
- Array.isArray(args["values"])
504
- ? args["values"].every(Boolean)
505
- : invalidArguments("and", context),
506
- or: (args, context) =>
507
- Array.isArray(args["values"])
508
- ? args["values"].some(Boolean)
509
- : invalidArguments("or", context),
510
- not: (args) => !args["value"],
511
- };
512
-
513
- export const evaluateA2uiValueFunction = (
514
- name: string,
515
- args: Record<string, unknown>,
516
- context: ValueFunctionContext,
517
- ): unknown => {
518
- if (!Object.hasOwn(VALUE_FUNCTIONS, name)) {
519
- context.warn(`A2UI function "${name}" is not supported and was skipped.`);
520
- return undefined;
521
- }
522
- return VALUE_FUNCTIONS[name]!(args, context);
523
- };
@@ -1,90 +0,0 @@
1
- import { describe, it, expect, vi } from "vitest";
2
- import { createActionRegistry, emptyActionRegistry } from "./actionRegistry";
3
-
4
- describe("createActionRegistry", () => {
5
- it("dispatch resolves a registered action type and calls its handler with the payload", () => {
6
- const handler = vi.fn(() => "ok");
7
- const registry = createActionRegistry({ purchase: handler });
8
-
9
- const result = registry.dispatch({
10
- type: "purchase",
11
- itemId: "sku-1",
12
- });
13
-
14
- expect(handler).toHaveBeenCalledWith({
15
- payload: { type: "purchase", itemId: "sku-1" },
16
- });
17
- expect(result).toBe("ok");
18
- });
19
-
20
- it("dispatch returns the handler's resolved promise value for async handlers", async () => {
21
- const registry = createActionRegistry({
22
- fetch: async () => 42,
23
- });
24
-
25
- const result = await registry.dispatch({ type: "fetch" });
26
- expect(result).toBe(42);
27
- });
28
-
29
- it("dispatch is a no-op (returns undefined) for an unknown action type", () => {
30
- const warn = vi.spyOn(console, "warn").mockImplementation(() => {});
31
- try {
32
- const registry = createActionRegistry({
33
- purchase: () => undefined,
34
- refund: () => undefined,
35
- });
36
- expect(registry.dispatch({ type: "unknown" })).toBeUndefined();
37
- expect(warn).toHaveBeenCalledTimes(1);
38
- expect(warn).toHaveBeenCalledWith(
39
- '[@assistant-ui/react-generative-ui] Action "unknown" has no registered handler. Registered actions: "purchase", "refund". Register it with createActionRegistry(...) or update the emitted `$action.type`.',
40
- );
41
- } finally {
42
- warn.mockRestore();
43
- }
44
- });
45
-
46
- it("dispatch is a no-op for malformed non-string action types", () => {
47
- const warn = vi.spyOn(console, "warn").mockImplementation(() => {});
48
- try {
49
- const handler = vi.fn();
50
- const registry = createActionRegistry({ purchase: handler });
51
-
52
- expect(registry.dispatch({ type: 123 } as never)).toBeUndefined();
53
-
54
- expect(handler).not.toHaveBeenCalled();
55
- expect(warn).toHaveBeenCalledTimes(1);
56
- expect(warn).toHaveBeenCalledWith(
57
- "[@assistant-ui/react-generative-ui] Skipping malformed action; `$action.type` must be a string. Received number. Update the emitted `$action` payload.",
58
- );
59
- } finally {
60
- warn.mockRestore();
61
- }
62
- });
63
-
64
- it("has reports whether a handler is registered", () => {
65
- const registry = createActionRegistry({ a: () => undefined });
66
- expect(registry.has("a")).toBe(true);
67
- expect(registry.has("b")).toBe(false);
68
- });
69
- });
70
-
71
- describe("emptyActionRegistry", () => {
72
- it("dispatch is a no-op for any type and emits the dev warning", () => {
73
- const warn = vi.spyOn(console, "warn").mockImplementation(() => {});
74
- try {
75
- expect(
76
- emptyActionRegistry.dispatch({ type: "anything" }),
77
- ).toBeUndefined();
78
- expect(warn).toHaveBeenCalledTimes(1);
79
- expect(warn).toHaveBeenCalledWith(
80
- '[@assistant-ui/react-generative-ui] Action "anything" has no registered handler. No actions are registered. Register it with createActionRegistry(...) or update the emitted `$action.type`.',
81
- );
82
- } finally {
83
- warn.mockRestore();
84
- }
85
- });
86
-
87
- it("has is false for any type", () => {
88
- expect(emptyActionRegistry.has("anything")).toBe(false);
89
- });
90
- });
@@ -1,88 +0,0 @@
1
- import type { Action } from "./ir";
2
-
3
- /** Context handed to an {@link ActionHandler} when an `$action` fires. */
4
- export type ActionDispatchContext = {
5
- /**
6
- * The action payload. For fire-and-forget actions this is the `$action` as the model emitted it. For an interactive component the user's runtime input is merged in under the reserved `$input` key: a single value for a standalone control's dispatch, or an object keyed by each field's `name` for a `Form` or a `Card` with `asForm` set. A model-supplied `value` field is never clobbered.
7
- */
8
- readonly payload: Action;
9
- };
10
-
11
- /**
12
- * Resolves a single `$action.type`. Fire-and-forget actions return `void` or a
13
- * promise that resolves to it. For a `prompt_user` tool whose call has no
14
- * result yet, a non-`undefined` return value becomes the tool result. Whether
15
- * that resumes the run depends on the runtime, including how it treats human
16
- * tools. The return value is handed back to `dispatch`'s caller as `unknown`.
17
- */
18
- export type ActionHandler = (
19
- ctx: ActionDispatchContext,
20
- ) => unknown | Promise<unknown>;
21
-
22
- /**
23
- * The host-provided map from `$action.type` to {@link ActionHandler}. This is the single dispatch target for rendered generative UI: every interactive component fires its `$action` here through the injected `$dispatch` (a submit Button defers to its ancestor form's dispatch instead). The payload's `$input` takes one of two shapes: a single value for a standalone control's dispatch (`Select`, `Input`, `DatePicker`, `Checkbox`, `RadioGroup`, or a `CheckboxGroup`'s array of checked values), or an object keyed by field `name` for a `Form` or a `Card` with `asForm` set. When a registry is used in a `JSONGenerativeUI` tool, each dispatched payload, including `$input`, is recorded on the tool call on a best-effort basis. Recording depends on the runtime and interaction log limits, and never delays or prevents dispatch. Construct it with {@link createActionRegistry} and pass it to `JSONGenerativeUI`.
24
- */
25
- export type ActionRegistry = {
26
- dispatch(action: Action): unknown;
27
- has(type: string): boolean;
28
- };
29
-
30
- export function createActionRegistry(
31
- handlers: Readonly<Record<string, ActionHandler>>,
32
- ): ActionRegistry {
33
- const map = new Map(Object.entries(handlers));
34
- return {
35
- dispatch(action) {
36
- if (typeof action?.type !== "string") {
37
- if (process.env["NODE_ENV"] !== "production") {
38
- // eslint-disable-next-line no-console
39
- console.warn(
40
- "[@assistant-ui/react-generative-ui] Skipping malformed action; " +
41
- "`$action.type` must be a string. " +
42
- `Received ${formatReceivedType(action?.type)}. Update the emitted ` +
43
- "`$action` payload.",
44
- );
45
- }
46
- return undefined;
47
- }
48
-
49
- const handler = map.get(action.type);
50
- if (!handler) {
51
- if (process.env["NODE_ENV"] !== "production") {
52
- const actionTypes = [...map.keys()];
53
- // eslint-disable-next-line no-console
54
- console.warn(
55
- `[@assistant-ui/react-generative-ui] Action "${action.type}" has ` +
56
- `no registered handler. ${formatRegisteredActions(actionTypes)} ` +
57
- "Register it with createActionRegistry(...) or update the emitted " +
58
- "`$action.type`.",
59
- );
60
- }
61
- return undefined;
62
- }
63
- return handler({ payload: action });
64
- },
65
- has(type) {
66
- return map.has(type);
67
- },
68
- };
69
- }
70
-
71
- /** A no-op registry used when no handlers are provided. Dispatch resolves to
72
- * `undefined` and logs a warning in dev for unknown types, so a model-emitted
73
- * action with no handler degrades to "does nothing" rather than throwing. */
74
- export const emptyActionRegistry: ActionRegistry = createActionRegistry({});
75
-
76
- function formatRegisteredActions(actionTypes: string[]) {
77
- if (actionTypes.length === 0) return "No actions are registered.";
78
-
79
- return `Registered actions: ${actionTypes
80
- .map((type) => `"${type}"`)
81
- .join(", ")}.`;
82
- }
83
-
84
- function formatReceivedType(value: unknown) {
85
- if (value === null) return "null";
86
- if (Array.isArray(value)) return "array";
87
- return typeof value;
88
- }