@assistant-ui/react-generative-ui 0.0.19 → 0.0.21

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 (137) hide show
  1. package/dist/JSONGenerativeUI.client.d.ts +14 -15
  2. package/dist/JSONGenerativeUI.client.d.ts.map +1 -1
  3. package/dist/JSONGenerativeUI.client.js +28 -8
  4. package/dist/JSONGenerativeUI.client.js.map +1 -1
  5. package/dist/JSONGenerativeUI.server.d.ts +5 -7
  6. package/dist/JSONGenerativeUI.server.d.ts.map +1 -1
  7. package/dist/JSONGenerativeUI.shared.d.ts +35 -36
  8. package/dist/JSONGenerativeUI.shared.d.ts.map +1 -1
  9. package/dist/a2ui/convert.d.ts +7 -7
  10. package/dist/a2ui/convert.d.ts.map +1 -1
  11. package/dist/a2ui/convert.js +148 -28
  12. package/dist/a2ui/convert.js.map +1 -1
  13. package/dist/a2ui/reducer.d.ts +1 -3
  14. package/dist/a2ui/reducer.d.ts.map +1 -1
  15. package/dist/a2ui/reducer.js +8 -0
  16. package/dist/a2ui/reducer.js.map +1 -1
  17. package/dist/a2ui/snapshot.d.ts +3 -0
  18. package/dist/a2ui/snapshot.d.ts.map +1 -0
  19. package/dist/a2ui/snapshot.js +33 -0
  20. package/dist/a2ui/snapshot.js.map +1 -0
  21. package/dist/a2ui/types.d.ts +50 -40
  22. package/dist/a2ui/types.d.ts.map +1 -1
  23. package/dist/a2ui/types.js.map +1 -1
  24. package/dist/a2ui.d.ts +5 -4
  25. package/dist/a2ui.d.ts.map +1 -0
  26. package/dist/a2ui.js +2 -1
  27. package/dist/actionRegistry.d.ts +12 -14
  28. package/dist/actionRegistry.d.ts.map +1 -1
  29. package/dist/actionRegistry.js.map +1 -1
  30. package/dist/buildPresentParameters.d.ts +2 -4
  31. package/dist/buildPresentParameters.d.ts.map +1 -1
  32. package/dist/buildPresentParameters.js +16 -4
  33. package/dist/buildPresentParameters.js.map +1 -1
  34. package/dist/constants.d.ts +0 -2
  35. package/dist/constants.d.ts.map +1 -1
  36. package/dist/convert/boundSpec.d.ts +0 -2
  37. package/dist/convert/boundSpec.d.ts.map +1 -1
  38. package/dist/convert/copyBounded.d.ts +2 -4
  39. package/dist/convert/copyBounded.d.ts.map +1 -1
  40. package/dist/convert/isElement.d.ts +1 -3
  41. package/dist/convert/isElement.d.ts.map +1 -1
  42. package/dist/convert/takeRun.d.ts +3 -5
  43. package/dist/convert/takeRun.d.ts.map +1 -1
  44. package/dist/defineGenerativeComponents.d.ts +1 -3
  45. package/dist/defineGenerativeComponents.d.ts.map +1 -1
  46. package/dist/generativeUIToJSX.d.ts +4 -6
  47. package/dist/generativeUIToJSX.d.ts.map +1 -1
  48. package/dist/index.d.ts +3 -13
  49. package/dist/index.d.ts.map +1 -0
  50. package/dist/index.server.d.ts +3 -13
  51. package/dist/index.server.d.ts.map +1 -0
  52. package/dist/index.shared.d.ts +15 -11
  53. package/dist/index.shared.d.ts.map +1 -0
  54. package/dist/ir.d.ts +17 -19
  55. package/dist/ir.d.ts.map +1 -1
  56. package/dist/renderGenerativeUI.d.ts +2 -4
  57. package/dist/renderGenerativeUI.d.ts.map +1 -1
  58. package/dist/scopeSchema.d.ts +6 -0
  59. package/dist/scopeSchema.d.ts.map +1 -0
  60. package/dist/scopeSchema.js +56 -0
  61. package/dist/scopeSchema.js.map +1 -0
  62. package/dist/slack/constants.d.ts +8 -9
  63. package/dist/slack/constants.d.ts.map +1 -1
  64. package/dist/slack/decodeBlockAction.d.ts +1 -3
  65. package/dist/slack/decodeBlockAction.d.ts.map +1 -1
  66. package/dist/slack/fromSlackBlocks.d.ts +1 -3
  67. package/dist/slack/fromSlackBlocks.d.ts.map +1 -1
  68. package/dist/slack/toSlackBlocks.d.ts +1 -3
  69. package/dist/slack/toSlackBlocks.d.ts.map +1 -1
  70. package/dist/slack/toSlackBlocks.js +1 -1
  71. package/dist/slack/types.d.ts +78 -80
  72. package/dist/slack/types.d.ts.map +1 -1
  73. package/dist/slack.d.ts +5 -5
  74. package/dist/slack.d.ts.map +1 -0
  75. package/dist/teams/constants.d.ts +1 -3
  76. package/dist/teams/constants.d.ts.map +1 -1
  77. package/dist/teams/decodeSubmitData.d.ts +1 -3
  78. package/dist/teams/decodeSubmitData.d.ts.map +1 -1
  79. package/dist/teams/toAdaptiveCard.d.ts +4 -6
  80. package/dist/teams/toAdaptiveCard.d.ts.map +1 -1
  81. package/dist/teams/toTeamsAttachments.d.ts +1 -3
  82. package/dist/teams/toTeamsAttachments.d.ts.map +1 -1
  83. package/dist/teams/types.d.ts +108 -109
  84. package/dist/teams/types.d.ts.map +1 -1
  85. package/dist/teams.d.ts +5 -5
  86. package/dist/teams.d.ts.map +1 -0
  87. package/dist/types.d.ts +36 -37
  88. package/dist/types.d.ts.map +1 -1
  89. package/dist/vocabulary/alert.d.ts +21 -23
  90. package/dist/vocabulary/alert.d.ts.map +1 -1
  91. package/dist/vocabulary/collectFormValues.d.ts +8 -10
  92. package/dist/vocabulary/collectFormValues.d.ts.map +1 -1
  93. package/dist/vocabulary/data.d.ts +45 -47
  94. package/dist/vocabulary/data.d.ts.map +1 -1
  95. package/dist/vocabulary/dispatch.d.ts +2 -4
  96. package/dist/vocabulary/dispatch.d.ts.map +1 -1
  97. package/dist/vocabulary/fact.d.ts +8 -10
  98. package/dist/vocabulary/fact.d.ts.map +1 -1
  99. package/dist/vocabulary/form.d.ts +7 -9
  100. package/dist/vocabulary/form.d.ts.map +1 -1
  101. package/dist/vocabulary/icon.d.ts +37 -39
  102. package/dist/vocabulary/icon.d.ts.map +1 -1
  103. package/dist/vocabulary/index.d.ts +1 -3
  104. package/dist/vocabulary/index.d.ts.map +1 -1
  105. package/dist/vocabulary/interactive.d.ts +87 -86
  106. package/dist/vocabulary/interactive.d.ts.map +1 -1
  107. package/dist/vocabulary/interactive.js +4 -4
  108. package/dist/vocabulary/interactive.js.map +1 -1
  109. package/dist/vocabulary/layout.d.ts +75 -77
  110. package/dist/vocabulary/layout.d.ts.map +1 -1
  111. package/dist/vocabulary/list.d.ts +10 -12
  112. package/dist/vocabulary/list.d.ts.map +1 -1
  113. package/dist/vocabulary/media.d.ts +21 -23
  114. package/dist/vocabulary/media.d.ts.map +1 -1
  115. package/dist/vocabulary/text.d.ts +53 -55
  116. package/dist/vocabulary/text.d.ts.map +1 -1
  117. package/dist/vocabulary/toTextContent.d.ts +0 -2
  118. package/dist/vocabulary/toTextContent.d.ts.map +1 -1
  119. package/package.json +4 -4
  120. package/src/JSONGenerativeUI.client.tsx +57 -15
  121. package/src/JSONGenerativeUI.test.tsx +298 -4
  122. package/src/a2ui/convert.test.ts +717 -0
  123. package/src/a2ui/convert.ts +229 -41
  124. package/src/a2ui/reducer.test.ts +18 -0
  125. package/src/a2ui/reducer.ts +16 -0
  126. package/src/a2ui/snapshot.test.ts +52 -0
  127. package/src/a2ui/snapshot.ts +52 -0
  128. package/src/a2ui/types.ts +16 -0
  129. package/src/a2ui.ts +2 -0
  130. package/src/actionRegistry.ts +5 -5
  131. package/src/buildPresentParameters.test.ts +246 -0
  132. package/src/buildPresentParameters.ts +18 -3
  133. package/src/scopeSchema.test.ts +72 -0
  134. package/src/scopeSchema.ts +71 -0
  135. package/src/vocabulary/interactive.dispatch.test.tsx +17 -3
  136. package/src/vocabulary/interactive.test.tsx +19 -0
  137. package/src/vocabulary/interactive.tsx +4 -1
@@ -1,5 +1,4 @@
1
- import { JSONGenerativeUIOptions, PresentTool, PresentToolOptions, PromptUserTool } from "./JSONGenerativeUI.shared.js";
2
- //#region src/JSONGenerativeUI.client.d.ts
1
+ import { type JSONGenerativeUIOptions, type PresentTool, type PresentToolOptions, type PromptUserTool } from "./JSONGenerativeUI.shared.js";
3
2
  /**
4
3
  * Client build of {@link JSONGenerativeUI}, resolved through the package's
5
4
  * `default` export condition (browser and SSR).
@@ -12,18 +11,18 @@ import { JSONGenerativeUIOptions, PresentTool, PresentToolOptions, PromptUserToo
12
11
  * interactive components can fire `$action` through `$dispatch`.
13
12
  */
14
13
  export declare class JSONGenerativeUI {
15
- private readonly library;
16
- private readonly parameters;
17
- private readonly actions;
18
- constructor(options: JSONGenerativeUIOptions);
19
- /**
20
- * The surface a `present` call paints into. It owns the vertical rhythm
21
- * between top-level blocks, which the host's message container does not
22
- * provide, and stays out of the way while it has nothing to show.
23
- */
24
- private readonly render;
25
- present(options?: PresentToolOptions): PresentTool;
26
- promptUser(): PromptUserTool;
14
+ private readonly library;
15
+ private readonly parameters;
16
+ private readonly actions;
17
+ private readonly completedPromptToolCallIds;
18
+ constructor(options: JSONGenerativeUIOptions);
19
+ /**
20
+ * The surface a `present` call paints into. It owns the vertical rhythm
21
+ * between top-level blocks, which the host's message container does not
22
+ * provide, and stays out of the way while it has nothing to show.
23
+ */
24
+ private readonly render;
25
+ present(options?: PresentToolOptions): PresentTool;
26
+ promptUser(): PromptUserTool;
27
27
  }
28
- //#endregion
29
28
  //# sourceMappingURL=JSONGenerativeUI.client.d.ts.map
@@ -1 +1 @@
1
- {"version":3,"file":"JSONGenerativeUI.client.d.ts","names":[],"sources":["../src/JSONGenerativeUI.client.tsx"],"mappings":";;;;;;;;;;;;;qBAkCa;mBACM;mBACA;mBACA;EAEL,YAAA,SAAS;;;;;;mBAWJ;EAejB,QAAQ,UAAU,qBAAqB;EASvC,cAAc"}
1
+ {"version":3,"file":"JSONGenerativeUI.client.d.ts","sourceRoot":"","sources":["../src/JSONGenerativeUI.client.tsx"],"names":[],"mappings":"AAGA,OAAO,EAGL,KAAK,uBAAuB,EAE5B,KAAK,WAAW,EAChB,KAAK,kBAAkB,EACvB,KAAK,cAAc,EACpB,qCAAkC;AAanC;;;;;;;;;;GAUG;AACH,qBAAa,gBAAgB;IAC3B,OAAO,CAAC,QAAQ,CAAC,OAAO,CAAsB;IAC9C,OAAO,CAAC,QAAQ,CAAC,UAAU,CAAoB;IAC/C,OAAO,CAAC,QAAQ,CAAC,OAAO,CAA6B;IACrD,OAAO,CAAC,QAAQ,CAAC,0BAA0B,CAAqB;gBAEpD,OAAO,EAAE,uBAAuB;IAM5C;;;;OAIG;IACH,OAAO,CAAC,QAAQ,CAAC,MAAM,CAqDrB;IAEF,OAAO,CAAC,OAAO,CAAC,EAAE,kBAAkB,GAAG,WAAW;IASlD,UAAU,IAAI,cAAc;CAO7B"}
@@ -25,6 +25,7 @@ var JSONGenerativeUI = class {
25
25
  library;
26
26
  parameters;
27
27
  actions;
28
+ completedPromptToolCallIds = /* @__PURE__ */ new Set();
28
29
  constructor(options) {
29
30
  this.library = options.library;
30
31
  this.parameters = buildPresentParameters(options.library);
@@ -35,13 +36,32 @@ var JSONGenerativeUI = class {
35
36
  * between top-level blocks, which the host's message container does not
36
37
  * provide, and stays out of the way while it has nothing to show.
37
38
  */
38
- render = ({ args, status }) => /* @__PURE__ */ jsx("div", {
39
- "data-aui": "root",
40
- children: renderGenerativeUI(args, this.library, {
41
- status: uiStatus(status),
42
- ...this.actions ? { dispatch: this.actions.dispatch } : {}
43
- })
44
- });
39
+ render = ({ args, status, addResult, result, toolCallId, unstable_recordInteraction }, completesPrompt = false) => {
40
+ const actions = this.actions;
41
+ const dispatch = actions ? (action) => {
42
+ if (unstable_recordInteraction) try {
43
+ unstable_recordInteraction({
44
+ type: "action",
45
+ payload: action
46
+ }).catch(() => {});
47
+ } catch {}
48
+ const actionResult = actions.dispatch(action);
49
+ if (completesPrompt && actionResult !== void 0 && result === void 0) Promise.resolve(actionResult).then((response) => {
50
+ if (response !== void 0 && !this.completedPromptToolCallIds.has(toolCallId)) {
51
+ this.completedPromptToolCallIds.add(toolCallId);
52
+ addResult(response);
53
+ }
54
+ }).catch(() => {});
55
+ return actionResult;
56
+ } : void 0;
57
+ return /* @__PURE__ */ jsx("div", {
58
+ "data-aui": "root",
59
+ children: renderGenerativeUI(args, this.library, {
60
+ status: uiStatus(status),
61
+ ...dispatch ? { dispatch } : {}
62
+ })
63
+ });
64
+ };
45
65
  present(options) {
46
66
  return {
47
67
  ...presentToolBase(this.parameters, options),
@@ -54,7 +74,7 @@ var JSONGenerativeUI = class {
54
74
  return {
55
75
  ...promptUserToolBase(this.parameters),
56
76
  unstable_backendDefault: { parameters: true },
57
- render: this.render
77
+ render: (props) => this.render(props, true)
58
78
  };
59
79
  }
60
80
  };
@@ -1 +1 @@
1
- {"version":3,"file":"JSONGenerativeUI.client.js","names":[],"sources":["../src/JSONGenerativeUI.client.tsx"],"sourcesContent":["import type { ReactNode } from \"react\";\nimport { buildPresentParameters } from \"./buildPresentParameters\";\nimport {\n presentToolBase,\n promptUserToolBase,\n type JSONGenerativeUIOptions,\n type PresentParameters,\n type PresentTool,\n type PresentToolOptions,\n type PromptUserTool,\n} from \"./JSONGenerativeUI.shared\";\nimport { type ActionRegistry } from \"./actionRegistry\";\nimport { renderGenerativeUI } from \"./renderGenerativeUI\";\nimport type { GenerativeUILibrary, GenerativeUIStatus } from \"./types\";\n\n/** Maps a tool-call part status to the generative-UI streaming status. Only a\n * `complete` call has fully-arrived args; `running` and `incomplete`\n * (aborted/errored, so args may be partial) both render as `\"streaming\"` so a\n * non-streaming component is never handed partial props. */\nfunction uiStatus(status: { type: string }): GenerativeUIStatus {\n return status.type === \"complete\" ? \"done\" : \"streaming\";\n}\n\n/**\n * Client build of {@link JSONGenerativeUI}, resolved through the package's\n * `default` export condition (browser and SSR).\n *\n * `present` is a frontend tool that renders the model's `{ $type, ...props }`\n * tree against the library and resolves immediately. `prompt_user` is a\n * human-in-the-loop tool: the model pauses and the rendered UI supplies the\n * result. Both draw the tree the same way, so they share one `render`. The\n * `actions` registry (if provided) is threaded into the render context so\n * interactive components can fire `$action` through `$dispatch`.\n */\nexport class JSONGenerativeUI {\n private readonly library: GenerativeUILibrary;\n private readonly parameters: PresentParameters;\n private readonly actions: ActionRegistry | undefined;\n\n constructor(options: JSONGenerativeUIOptions) {\n this.library = options.library;\n this.parameters = buildPresentParameters(options.library);\n this.actions = options.actions;\n }\n\n /**\n * The surface a `present` call paints into. It owns the vertical rhythm\n * between top-level blocks, which the host's message container does not\n * provide, and stays out of the way while it has nothing to show.\n */\n private readonly render = ({\n args,\n status,\n }: {\n args: unknown;\n status: { type: string };\n }): ReactNode => (\n <div data-aui=\"root\">\n {renderGenerativeUI(args, this.library, {\n status: uiStatus(status),\n ...(this.actions ? { dispatch: this.actions.dispatch } : {}),\n })}\n </div>\n );\n\n present(options?: PresentToolOptions): PresentTool {\n return {\n ...presentToolBase(this.parameters, options),\n unstable_backendDefault: { parameters: true },\n execute: async () => ({}),\n render: this.render,\n };\n }\n\n promptUser(): PromptUserTool {\n return {\n ...promptUserToolBase(this.parameters),\n unstable_backendDefault: { parameters: true },\n render: this.render,\n };\n }\n}\n"],"mappings":";;;;;;;;;AAmBA,SAAS,SAAS,QAA8C;CAC9D,OAAO,OAAO,SAAS,aAAa,SAAS;AAC/C;;;;;;;;;;;;AAaA,IAAa,mBAAb,MAA8B;CAC5B;CACA;CACA;CAEA,YAAY,SAAkC;EAC5C,KAAK,UAAU,QAAQ;EACvB,KAAK,aAAa,uBAAuB,QAAQ,OAAO;EACxD,KAAK,UAAU,QAAQ;CACzB;;;;;;CAOA,UAA2B,EACzB,MACA,aAKA,oBAAC,OAAD;EAAK,YAAS;EACX,UAAA,mBAAmB,MAAM,KAAK,SAAS;GACtC,QAAQ,SAAS,MAAM;GACvB,GAAI,KAAK,UAAU,EAAE,UAAU,KAAK,QAAQ,SAAS,IAAI,CAAC;EAC5D,CAAC;CACE,CAAA;CAGP,QAAQ,SAA2C;EACjD,OAAO;GACL,GAAG,gBAAgB,KAAK,YAAY,OAAO;GAC3C,yBAAyB,EAAE,YAAY,KAAK;GAC5C,SAAS,aAAa,CAAC;GACvB,QAAQ,KAAK;EACf;CACF;CAEA,aAA6B;EAC3B,OAAO;GACL,GAAG,mBAAmB,KAAK,UAAU;GACrC,yBAAyB,EAAE,YAAY,KAAK;GAC5C,QAAQ,KAAK;EACf;CACF;AACF"}
1
+ {"version":3,"file":"JSONGenerativeUI.client.js","names":[],"sources":["../src/JSONGenerativeUI.client.tsx"],"sourcesContent":["import type { ToolCallMessagePartProps } from \"@assistant-ui/react\";\nimport type { ReactNode } from \"react\";\nimport { buildPresentParameters } from \"./buildPresentParameters\";\nimport {\n presentToolBase,\n promptUserToolBase,\n type JSONGenerativeUIOptions,\n type PresentParameters,\n type PresentTool,\n type PresentToolOptions,\n type PromptUserTool,\n} from \"./JSONGenerativeUI.shared\";\nimport { type ActionRegistry } from \"./actionRegistry\";\nimport { renderGenerativeUI } from \"./renderGenerativeUI\";\nimport type { GenerativeUILibrary, GenerativeUIStatus } from \"./types\";\n\n/** Maps a tool-call part status to the generative-UI streaming status. Only a\n * `complete` call has fully-arrived args; `running` and `incomplete`\n * (aborted/errored, so args may be partial) both render as `\"streaming\"` so a\n * non-streaming component is never handed partial props. */\nfunction uiStatus(status: { type: string }): GenerativeUIStatus {\n return status.type === \"complete\" ? \"done\" : \"streaming\";\n}\n\n/**\n * Client build of {@link JSONGenerativeUI}, resolved through the package's\n * `default` export condition (browser and SSR).\n *\n * `present` is a frontend tool that renders the model's `{ $type, ...props }`\n * tree against the library and resolves immediately. `prompt_user` is a\n * human-in-the-loop tool: the model pauses and the rendered UI supplies the\n * result. Both draw the tree the same way, so they share one `render`. The\n * `actions` registry (if provided) is threaded into the render context so\n * interactive components can fire `$action` through `$dispatch`.\n */\nexport class JSONGenerativeUI {\n private readonly library: GenerativeUILibrary;\n private readonly parameters: PresentParameters;\n private readonly actions: ActionRegistry | undefined;\n private readonly completedPromptToolCallIds = new Set<string>();\n\n constructor(options: JSONGenerativeUIOptions) {\n this.library = options.library;\n this.parameters = buildPresentParameters(options.library);\n this.actions = options.actions;\n }\n\n /**\n * The surface a `present` call paints into. It owns the vertical rhythm\n * between top-level blocks, which the host's message container does not\n * provide, and stays out of the way while it has nothing to show.\n */\n private readonly render = (\n {\n args,\n status,\n addResult,\n result,\n toolCallId,\n unstable_recordInteraction,\n }: ToolCallMessagePartProps<Record<string, unknown>, any>,\n completesPrompt = false,\n ): ReactNode => {\n const actions = this.actions;\n const dispatch = actions\n ? (action: Parameters<ActionRegistry[\"dispatch\"]>[0]) => {\n if (unstable_recordInteraction) {\n try {\n void unstable_recordInteraction({\n type: \"action\",\n payload: action,\n }).catch(() => {});\n } catch {}\n }\n\n const actionResult = actions.dispatch(action);\n if (\n completesPrompt &&\n actionResult !== undefined &&\n result === undefined\n ) {\n void Promise.resolve(actionResult)\n .then((response) => {\n if (\n response !== undefined &&\n !this.completedPromptToolCallIds.has(toolCallId)\n ) {\n this.completedPromptToolCallIds.add(toolCallId);\n addResult(response);\n }\n })\n .catch(() => {});\n }\n return actionResult;\n }\n : undefined;\n\n return (\n <div data-aui=\"root\">\n {renderGenerativeUI(args, this.library, {\n status: uiStatus(status),\n ...(dispatch ? { dispatch } : {}),\n })}\n </div>\n );\n };\n\n present(options?: PresentToolOptions): PresentTool {\n return {\n ...presentToolBase(this.parameters, options),\n unstable_backendDefault: { parameters: true },\n execute: async () => ({}),\n render: this.render,\n };\n }\n\n promptUser(): PromptUserTool {\n return {\n ...promptUserToolBase(this.parameters),\n unstable_backendDefault: { parameters: true },\n render: (props) => this.render(props, true),\n };\n }\n}\n"],"mappings":";;;;;;;;;AAoBA,SAAS,SAAS,QAA8C;CAC9D,OAAO,OAAO,SAAS,aAAa,SAAS;AAC/C;;;;;;;;;;;;AAaA,IAAa,mBAAb,MAA8B;CAC5B;CACA;CACA;CACA,6CAA8C,IAAI,IAAY;CAE9D,YAAY,SAAkC;EAC5C,KAAK,UAAU,QAAQ;EACvB,KAAK,aAAa,uBAAuB,QAAQ,OAAO;EACxD,KAAK,UAAU,QAAQ;CACzB;;;;;;CAOA,UACE,EACE,MACA,QACA,WACA,QACA,YACA,8BAEF,kBAAkB,UACJ;EACd,MAAM,UAAU,KAAK;EACrB,MAAM,WAAW,WACZ,WAAsD;GACrD,IAAI,4BACF,IAAI;IACF,2BAAgC;KAC9B,MAAM;KACN,SAAS;IACX,CAAC,CAAC,CAAC,YAAY,CAAC,CAAC;GACnB,QAAQ,CAAC;GAGX,MAAM,eAAe,QAAQ,SAAS,MAAM;GAC5C,IACE,mBACA,iBAAiB,KAAA,KACjB,WAAW,KAAA,GAEX,QAAa,QAAQ,YAAY,CAAC,CAC/B,MAAM,aAAa;IAClB,IACE,aAAa,KAAA,KACb,CAAC,KAAK,2BAA2B,IAAI,UAAU,GAC/C;KACA,KAAK,2BAA2B,IAAI,UAAU;KAC9C,UAAU,QAAQ;IACpB;GACF,CAAC,CAAC,CACD,YAAY,CAAC,CAAC;GAEnB,OAAO;EACT,IACA,KAAA;EAEJ,OACE,oBAAC,OAAD;GAAK,YAAS;GACX,UAAA,mBAAmB,MAAM,KAAK,SAAS;IACtC,QAAQ,SAAS,MAAM;IACvB,GAAI,WAAW,EAAE,SAAS,IAAI,CAAC;GACjC,CAAC;EACE,CAAA;CAET;CAEA,QAAQ,SAA2C;EACjD,OAAO;GACL,GAAG,gBAAgB,KAAK,YAAY,OAAO;GAC3C,yBAAyB,EAAE,YAAY,KAAK;GAC5C,SAAS,aAAa,CAAC;GACvB,QAAQ,KAAK;EACf;CACF;CAEA,aAA6B;EAC3B,OAAO;GACL,GAAG,mBAAmB,KAAK,UAAU;GACrC,yBAAyB,EAAE,YAAY,KAAK;GAC5C,SAAS,UAAU,KAAK,OAAO,OAAO,IAAI;EAC5C;CACF;AACF"}
@@ -1,5 +1,4 @@
1
- import { JSONGenerativeUIOptions, PresentTool, PresentToolOptions, PromptUserTool } from "./JSONGenerativeUI.shared.js";
2
- //#region src/JSONGenerativeUI.server.d.ts
1
+ import { type JSONGenerativeUIOptions, type PresentTool, type PresentToolOptions, type PromptUserTool } from "./JSONGenerativeUI.shared.js";
3
2
  /**
4
3
  * Server build of {@link JSONGenerativeUI}, resolved through the package's
5
4
  * `react-server` export condition.
@@ -14,10 +13,9 @@ import { JSONGenerativeUIOptions, PresentTool, PresentToolOptions, PromptUserToo
14
13
  * tools; on the server it is structurally absent and never read.
15
14
  */
16
15
  export declare class JSONGenerativeUI {
17
- private readonly parameters;
18
- constructor(options: JSONGenerativeUIOptions);
19
- present(options?: PresentToolOptions): PresentTool;
20
- promptUser(): PromptUserTool;
16
+ private readonly parameters;
17
+ constructor(options: JSONGenerativeUIOptions);
18
+ present(options?: PresentToolOptions): PresentTool;
19
+ promptUser(): PromptUserTool;
21
20
  }
22
- //#endregion
23
21
  //# sourceMappingURL=JSONGenerativeUI.server.d.ts.map
@@ -1 +1 @@
1
- {"version":3,"file":"JSONGenerativeUI.server.d.ts","names":[],"sources":["../src/JSONGenerativeUI.server.tsx"],"mappings":";;;;;;;;;;;;;;;qBAwBa;mBACM;EAEL,YAAA,SAAS;EAIrB,QAAQ,UAAU,qBAAqB;EAIvC,cAAc"}
1
+ {"version":3,"file":"JSONGenerativeUI.server.d.ts","sourceRoot":"","sources":["../src/JSONGenerativeUI.server.tsx"],"names":[],"mappings":"AACA,OAAO,EAGL,KAAK,uBAAuB,EAE5B,KAAK,WAAW,EAChB,KAAK,kBAAkB,EACvB,KAAK,cAAc,EACpB,qCAAkC;AAEnC;;;;;;;;;;;;GAYG;AACH,qBAAa,gBAAgB;IAC3B,OAAO,CAAC,QAAQ,CAAC,UAAU,CAAoB;gBAEnC,OAAO,EAAE,uBAAuB;IAI5C,OAAO,CAAC,OAAO,CAAC,EAAE,kBAAkB,GAAG,WAAW;IAIlD,UAAU,IAAI,cAAc;CAG7B"}
@@ -1,38 +1,37 @@
1
- import { ActionRegistry } from "./actionRegistry.js";
2
- import { GenerativeUILibrary } from "./types.js";
3
- import { ToolDefinition } from "@assistant-ui/react";
4
- import { JSONSchema7 } from "json-schema";
5
- //#region src/JSONGenerativeUI.shared.d.ts
1
+ import type { ToolDefinition } from "@assistant-ui/react";
2
+ import type { JSONSchema7 } from "json-schema";
3
+ import type { ActionRegistry } from "./actionRegistry.js";
4
+ import type { GenerativeUILibrary } from "./types.js";
6
5
  /** Options for {@link JSONGenerativeUI}. */
7
6
  export type JSONGenerativeUIOptions = {
8
- /**
9
- * The components the model is allowed to render, keyed by the `$type` it
10
- * selects them with. Author it with `defineGenerativeComponents({ ... })` so a
11
- * `"use generative"` build can split each `render` from its `properties`.
12
- */
13
- library: GenerativeUILibrary;
14
- /**
15
- * Host-provided map from `$action.type` to its handler. Interactive
16
- * components (`Button`/`Select`/`Input`/`DatePicker`) call `$dispatch($action)`
17
- * on their event, which resolves through this registry. Omit it for a
18
- * read-only render where model-emitted actions degrade to a no-op.
19
- */
20
- actions?: ActionRegistry;
7
+ /**
8
+ * The components the model is allowed to render, keyed by the `$type` it
9
+ * selects them with. Author it with `defineGenerativeComponents({ ... })` so a
10
+ * `"use generative"` build can split each `render` from its `properties`.
11
+ */
12
+ library: GenerativeUILibrary;
13
+ /**
14
+ * Host-provided map from `$action.type` to its handler. Interactive
15
+ * components (`Button`/`Select`/`Input`/`DatePicker`) call `$dispatch($action)`
16
+ * on their event, which resolves through this registry. Omit it for a
17
+ * read-only render where model-emitted actions degrade to a no-op.
18
+ */
19
+ actions?: ActionRegistry;
21
20
  };
22
21
  /** Options for {@link JSONGenerativeUI.present}. */
23
22
  export type PresentToolOptions = {
24
- /**
25
- * Set `"standalone"` to render the component on its own surface (outside the
26
- * chain-of-thought trace), e.g. a full-bleed artifact like a card. Omit it for
27
- * the default inline rendering — there is no `"inline"` value because that is
28
- * already the default.
29
- */
30
- display?: "standalone";
23
+ /**
24
+ * Set `"standalone"` to render the component on its own surface (outside the
25
+ * chain-of-thought trace), e.g. a full-bleed artifact like a card. Omit it for
26
+ * the default inline rendering — there is no `"inline"` value because that is
27
+ * already the default.
28
+ */
29
+ display?: "standalone";
31
30
  };
32
31
  type BackendDefaultMetadata = {
33
- unstable_backendDefault?: {
34
- parameters?: boolean;
35
- };
32
+ unstable_backendDefault?: {
33
+ parameters?: boolean;
34
+ };
36
35
  };
37
36
  /** The `present` tool, as the model sees it (no client `render`/`execute`). */
38
37
  export type PresentTool = ToolDefinition<Record<string, unknown>, Record<string, never>> & BackendDefaultMetadata;
@@ -46,19 +45,19 @@ export type PresentParameters = JSONSchema7;
46
45
  * Takes the already-built `parameters` so it isn't recomputed per tool.
47
46
  */
48
47
  export declare function presentToolBase(parameters: PresentParameters, options?: PresentToolOptions): {
49
- display?: "standalone";
50
- type: "frontend";
51
- description: string;
52
- parameters: JSONSchema7;
48
+ display?: "standalone";
49
+ type: "frontend";
50
+ description: string;
51
+ parameters: JSONSchema7;
53
52
  };
54
53
  /**
55
54
  * The schema-only half of the `prompt_user` tool, shared by both builds. The
56
55
  * server build returns exactly this; the client build adds `render`.
57
56
  */
58
57
  export declare function promptUserToolBase(parameters: PresentParameters): {
59
- type: "human";
60
- description: string;
61
- parameters: JSONSchema7;
58
+ type: "human";
59
+ description: string;
60
+ parameters: JSONSchema7;
62
61
  };
63
- //#endregion
62
+ export {};
64
63
  //# sourceMappingURL=JSONGenerativeUI.shared.d.ts.map
@@ -1 +1 @@
1
- {"version":3,"file":"JSONGenerativeUI.shared.d.ts","names":[],"sources":["../src/JSONGenerativeUI.shared.ts"],"mappings":";;;;;;YAMY;;;;;;EAMV,SAAS;;;;;;;EAOT,UAAU;;;YAIA;;;;;;;EAOV;;KAGG;EACH;IACE;;;;YAKQ,cAAc,eACxB,yBACA,yBAEA;;YAGU,iBAAiB,eAAe,oCAC1C;;YAYU,oBAAoB;;;;;;wBAOhB,gBACd,YAAY,mBACZ,UAAU;;;;;;;;;;wBAcI,mBAAmB,YAAY;;;cAAA"}
1
+ {"version":3,"file":"JSONGenerativeUI.shared.d.ts","sourceRoot":"","sources":["../src/JSONGenerativeUI.shared.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,cAAc,EAAE,MAAM,qBAAqB,CAAC;AAC1D,OAAO,KAAK,EAAE,WAAW,EAAE,MAAM,aAAa,CAAC;AAC/C,OAAO,KAAK,EAAE,cAAc,EAAE,4BAAyB;AACvD,OAAO,KAAK,EAAE,mBAAmB,EAAE,mBAAgB;AAEnD,4CAA4C;AAC5C,MAAM,MAAM,uBAAuB,GAAG;IACpC;;;;OAIG;IACH,OAAO,EAAE,mBAAmB,CAAC;IAC7B;;;;;OAKG;IACH,OAAO,CAAC,EAAE,cAAc,CAAC;CAC1B,CAAC;AAEF,oDAAoD;AACpD,MAAM,MAAM,kBAAkB,GAAG;IAC/B;;;;;OAKG;IACH,OAAO,CAAC,EAAE,YAAY,CAAC;CACxB,CAAC;AAEF,KAAK,sBAAsB,GAAG;IAC5B,uBAAuB,CAAC,EAAE;QACxB,UAAU,CAAC,EAAE,OAAO,CAAC;KACtB,CAAC;CACH,CAAC;AAEF,+EAA+E;AAC/E,MAAM,MAAM,WAAW,GAAG,cAAc,CACtC,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,EACvB,MAAM,CAAC,MAAM,EAAE,KAAK,CAAC,CACtB,GACC,sBAAsB,CAAC;AAEzB,yEAAyE;AACzE,MAAM,MAAM,cAAc,GAAG,cAAc,CAAC,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,EAAE,OAAO,CAAC,GAC3E,sBAAsB,CAAC;AAWzB,4FAA4F;AAC5F,MAAM,MAAM,iBAAiB,GAAG,WAAW,CAAC;AAE5C;;;;GAIG;AACH,wBAAgB,eAAe,CAC7B,UAAU,EAAE,iBAAiB,EAC7B,OAAO,CAAC,EAAE,kBAAkB;;;;;EAQ7B;AAED;;;GAGG;AACH,wBAAgB,kBAAkB,CAAC,UAAU,EAAE,iBAAiB;;;;EAM/D"}
@@ -1,9 +1,9 @@
1
- import { UIElement } from "../ir.js";
2
- import { A2uiSurfaceState } from "./types.js";
3
- //#region src/a2ui/convert.d.ts
4
- export declare function convertSurfaceToUISpec(surface: A2uiSurfaceState): {
5
- spec: UIElement | null;
6
- warnings: string[];
1
+ import { type UIElement } from "../ir.js";
2
+ import { type A2uiSurfaceState } from "./types.js";
3
+ export declare function convertSurfaceToUISpec(surface: A2uiSurfaceState, options?: {
4
+ readonly keepUnknownComponents?: boolean;
5
+ }): {
6
+ spec: UIElement | null;
7
+ warnings: string[];
7
8
  };
8
- //#endregion
9
9
  //# sourceMappingURL=convert.d.ts.map
@@ -1 +1 @@
1
- {"version":3,"file":"convert.d.ts","names":[],"sources":["../../src/a2ui/convert.ts"],"mappings":";;;wBAscgB,uBAAuB,SAAS;EAC9C,MAAM;EACN"}
1
+ {"version":3,"file":"convert.d.ts","sourceRoot":"","sources":["../../src/a2ui/convert.ts"],"names":[],"mappings":"AAAA,OAAO,EAAc,KAAK,SAAS,EAAE,iBAAc;AACnD,OAAO,EAEL,KAAK,gBAAgB,EAEtB,mBAAgB;AAynBjB,wBAAgB,sBAAsB,CACpC,OAAO,EAAE,gBAAgB,EACzB,OAAO,GAAE;IAAE,QAAQ,CAAC,qBAAqB,CAAC,EAAE,OAAO,CAAA;CAAO,GACzD;IACD,IAAI,EAAE,SAAS,GAAG,IAAI,CAAC;IACvB,QAAQ,EAAE,MAAM,EAAE,CAAC;CACpB,CA+BA"}
@@ -1,3 +1,4 @@
1
+ import { ICON_NAMES } from "../ir.js";
1
2
  import { A2UI_SURFACE_ID } from "./types.js";
2
3
  //#region src/a2ui/convert.ts
3
4
  const DEPTH_CAP = 32;
@@ -6,13 +7,23 @@ const NODE_BUDGET = 5e3;
6
7
  const SUPPORTED_COMPONENTS = /* @__PURE__ */ new Set([
7
8
  "Text",
8
9
  "Image",
10
+ "Icon",
9
11
  "Row",
10
12
  "Column",
13
+ "List",
11
14
  "Card",
12
15
  "Divider",
13
16
  "Button",
14
17
  "TextField",
15
- "CheckBox"
18
+ "CheckBox",
19
+ "ChoicePicker",
20
+ "DateTimeInput"
21
+ ]);
22
+ const ICON_NAME_SET = new Set(ICON_NAMES);
23
+ const ICON_SIZE_SET = /* @__PURE__ */ new Set([
24
+ "sm",
25
+ "md",
26
+ "lg"
16
27
  ]);
17
28
  const isRecord = (value) => typeof value === "object" && value !== null && !Array.isArray(value);
18
29
  const isPlainObject = (value) => {
@@ -86,11 +97,17 @@ const reserveNode = (context) => {
86
97
  context.emittedNodes++;
87
98
  return true;
88
99
  };
89
- const childrenOf = (node, dataSource, context, depth, visited) => {
100
+ const childReferences = (node) => {
90
101
  const children = node["children"];
91
- if (!Array.isArray(children)) return [];
102
+ const references = Array.isArray(children) ? [...children] : [];
103
+ if (node["child"] !== void 0) references.push(node["child"]);
104
+ if (node["component"] === "Modal") references.push(node["trigger"], node["content"]);
105
+ if (node["component"] === "Tabs" && Array.isArray(node["tabs"])) for (const tab of node["tabs"]) references.push(isRecord(tab) ? tab["child"] : void 0);
106
+ return references;
107
+ };
108
+ const childrenOf = (node, dataSource, context, depth, visited) => {
92
109
  const result = [];
93
- for (const childId of children) {
110
+ for (const childId of childReferences(node)) {
94
111
  if (typeof childId !== "string") {
95
112
  context.warnings.push(`Component "${String(node["id"] ?? "")}" has a malformed child reference.`);
96
113
  continue;
@@ -100,21 +117,38 @@ const childrenOf = (node, dataSource, context, depth, visited) => {
100
117
  }
101
118
  return result;
102
119
  };
103
- const mappedAction = (node, props, dataSource, context) => {
120
+ const textLabel = (node, children, context) => {
121
+ const references = childReferences(node);
122
+ const [reference] = references;
123
+ const [child] = children;
124
+ if (references.length !== 1 || children.length !== 1 || typeof reference !== "string" || !child || context.surface.components.get(reference)?.["component"] !== "Text") return;
125
+ const text = child.$type === "Header" ? child["text"] : child["value"];
126
+ return typeof text === "string" ? text : void 0;
127
+ };
128
+ const mappedAction = (node, props, context) => {
104
129
  const action = props["action"];
105
- const actionName = typeof action === "string" ? action : isRecord(action) && typeof action["name"] === "string" ? action["name"] : void 0;
130
+ const event = isRecord(action) && isRecord(action["event"]) ? action["event"] : action;
131
+ const actionName = typeof event === "string" ? event : isRecord(event) && typeof event["name"] === "string" ? event["name"] : void 0;
106
132
  if (!actionName) return void 0;
107
- const rawContext = isRecord(action) ? action["context"] : void 0;
108
- const resolvedContext = rawContext === void 0 ? void 0 : materialize(rawContext, dataSource);
133
+ const actionContext = isRecord(event) ? event["context"] : void 0;
109
134
  return {
110
135
  type: "a2ui:action",
111
136
  name: actionName,
112
137
  surfaceId: context.surfaceId,
113
138
  sourceComponentId: typeof node["id"] === "string" ? node["id"] : "",
114
- ...resolvedContext !== void 0 ? { context: resolvedContext } : {}
139
+ ...actionContext !== void 0 ? { context: actionContext } : {}
115
140
  };
116
141
  };
117
- const mappedProps = (node, props, dataSource, context) => {
142
+ const choiceOptions = (value) => {
143
+ if (!Array.isArray(value)) return [];
144
+ const options = [];
145
+ for (const option of value) if (isRecord(option) && typeof option["label"] === "string" && typeof option["value"] === "string") options.push({
146
+ label: option["label"],
147
+ value: option["value"]
148
+ });
149
+ return options;
150
+ };
151
+ const mappedProps = (node, props, context) => {
118
152
  const component = node["component"];
119
153
  if (component === "Text") {
120
154
  const text = stringProp(props, ["text", "value"]);
@@ -134,7 +168,11 @@ const mappedProps = (node, props, dataSource, context) => {
134
168
  }
135
169
  if (component === "Image") {
136
170
  const src = stringProp(props, ["src", "url"]);
137
- const alt = stringProp(props, ["alt", "altText"]);
171
+ const alt = stringProp(props, [
172
+ "alt",
173
+ "altText",
174
+ "description"
175
+ ]);
138
176
  const size = props["size"];
139
177
  const round = props["round"];
140
178
  return {
@@ -145,6 +183,16 @@ const mappedProps = (node, props, dataSource, context) => {
145
183
  ...typeof round === "boolean" ? { round } : {}
146
184
  };
147
185
  }
186
+ if (component === "Icon") {
187
+ const name = stringProp(props, ["name"]);
188
+ if (!name || !ICON_NAME_SET.has(name)) return void 0;
189
+ const size = props["size"];
190
+ return {
191
+ $type: "Icon",
192
+ name,
193
+ ...typeof size === "string" && ICON_SIZE_SET.has(size) ? { size } : {}
194
+ };
195
+ }
148
196
  if (component === "Row") {
149
197
  const gap = props["gap"];
150
198
  const align = firstDefined(props, ["align", "alignment"]);
@@ -165,6 +213,15 @@ const mappedProps = (node, props, dataSource, context) => {
165
213
  ...typeof align === "string" ? { align } : {}
166
214
  };
167
215
  }
216
+ if (component === "List") {
217
+ const direction = props["direction"];
218
+ const align = props["align"];
219
+ if (direction === "horizontal") return {
220
+ $type: "Row",
221
+ ...typeof align === "string" ? { align } : {}
222
+ };
223
+ return { $type: "ListView" };
224
+ }
168
225
  if (component === "Card") {
169
226
  const title = props["title"];
170
227
  const padding = props["padding"];
@@ -185,7 +242,7 @@ const mappedProps = (node, props, dataSource, context) => {
185
242
  const buttonStyle = props["buttonStyle"];
186
243
  const block = props["block"];
187
244
  const submit = props["submit"];
188
- const action = mappedAction(node, props, dataSource, context);
245
+ const action = mappedAction(node, props, context);
189
246
  return {
190
247
  $type: "Button",
191
248
  ...label !== void 0 ? { label } : {},
@@ -222,14 +279,49 @@ const mappedProps = (node, props, dataSource, context) => {
222
279
  ...typeof defaultChecked === "boolean" ? { defaultChecked } : {}
223
280
  };
224
281
  }
282
+ if (component === "ChoicePicker") {
283
+ const options = choiceOptions(props["options"]);
284
+ const value = props["value"];
285
+ const defaultValue = typeof value === "string" ? value : Array.isArray(value) && typeof value[0] === "string" ? value[0] : void 0;
286
+ if (props["variant"] === "radio" || props["variant"] === "single" || props["variant"] === "singleSelection" || props["displayStyle"] === "radio" || props["variant"] === "mutuallyExclusive" && props["displayStyle"] !== "chips") return {
287
+ $type: "RadioGroup",
288
+ options,
289
+ ...label !== void 0 ? { label } : {},
290
+ ...name !== void 0 ? { name } : {},
291
+ ...defaultValue !== void 0 ? { defaultValue } : {}
292
+ };
293
+ const placeholder = stringProp(props, ["placeholder"]);
294
+ return {
295
+ $type: "Select",
296
+ options,
297
+ ...placeholder !== void 0 ? { placeholder } : {},
298
+ ...label !== void 0 ? { label } : {},
299
+ ...name !== void 0 ? { name } : {}
300
+ };
301
+ }
302
+ if (component === "DateTimeInput") {
303
+ const value = stringProp(props, ["value"]);
304
+ const min = stringProp(props, ["min"]);
305
+ const max = stringProp(props, ["max"]);
306
+ return {
307
+ $type: "DatePicker",
308
+ ...value !== void 0 ? { value } : {},
309
+ ...min !== void 0 ? { min } : {},
310
+ ...max !== void 0 ? { max } : {},
311
+ ...label !== void 0 ? { label } : {},
312
+ ...name !== void 0 ? { name } : {}
313
+ };
314
+ }
225
315
  };
226
- const convertTemplate = (node, templateChildren, dataSource, context, depth, visited) => {
316
+ const convertTemplate = (node, templateChildren, dataSource, context, depth, visited, retained, mappedContainer) => {
227
317
  if (!reserveNode(context)) return null;
318
+ const horizontalList = node["component"] === "List" && materialize(node["direction"], dataSource) === "horizontal";
319
+ const container = mappedContainer ?? retained ?? { $type: horizontalList ? "Row" : "ListView" };
228
320
  const list = resolvePointer(dataSource, templateChildren.template.path);
229
321
  if (!Array.isArray(list)) {
230
322
  context.warnings.push(`Template on component "${String(node["id"] ?? "")}" did not resolve to a list.`);
231
323
  return {
232
- $type: "ListView",
324
+ ...container,
233
325
  children: []
234
326
  };
235
327
  }
@@ -240,15 +332,17 @@ const convertTemplate = (node, templateChildren, dataSource, context, depth, vis
240
332
  }
241
333
  const children = [];
242
334
  for (let index = 0; index < itemCount; index++) {
243
- if (!reserveNode(context)) break;
335
+ if (!retained && !horizontalList && !reserveNode(context)) break;
244
336
  const child = convertComponent(templateChildren.template.componentId, list[index], context, depth + 1, visited);
245
- children.push({
337
+ if (retained || horizontalList) {
338
+ if (child) children.push(child);
339
+ } else children.push({
246
340
  $type: "ListViewItem",
247
341
  ...child ? { children: child } : {}
248
342
  });
249
343
  }
250
344
  return {
251
- $type: "ListView",
345
+ ...container,
252
346
  children
253
347
  };
254
348
  };
@@ -272,31 +366,56 @@ function convertComponent(componentId, dataSource, context, depth, visited) {
272
366
  const component = node["component"];
273
367
  visited.add(componentId);
274
368
  try {
275
- const templateChildren = node["children"];
276
- if (isTemplateChildren(templateChildren)) return convertTemplate(node, templateChildren, dataSource, context, depth, visited);
277
- if (typeof component !== "string" || !SUPPORTED_COMPONENTS.has(component)) {
369
+ if (typeof component !== "string") {
278
370
  context.warnings.push(`Unknown A2UI component "${String(component ?? "")}" was skipped.`);
279
371
  return null;
280
372
  }
281
- if (!reserveNode(context)) return null;
373
+ const templateChildren = node["children"];
374
+ const hasTemplate = isTemplateChildren(templateChildren);
375
+ if (!SUPPORTED_COMPONENTS.has(component) && !context.keepUnknownComponents && !hasTemplate) {
376
+ context.warnings.push(`Unknown A2UI component "${component}" was skipped.`);
377
+ return null;
378
+ }
282
379
  const props = {};
283
380
  for (const [key, value] of Object.entries(node)) {
284
- if (key === "id" || key === "component" || key === "children") continue;
381
+ if (key === "id" || key === "component" || key === "children" || key === "child") continue;
285
382
  const resolved = materialize(value, dataSource);
286
383
  if (resolved !== void 0) setOwnProperty(props, key, resolved);
287
384
  }
288
- const mapped = mappedProps(node, props, dataSource, context);
289
- if (!mapped) return null;
385
+ const mapped = mappedProps(node, props, context);
386
+ if (!mapped && SUPPORTED_COMPONENTS.has(component)) {
387
+ context.warnings.push(`A2UI component "${component}" could not be mapped and was skipped.`);
388
+ return null;
389
+ }
390
+ const retained = !mapped && context.keepUnknownComponents ? {
391
+ $type: component,
392
+ ...Object.fromEntries(Object.entries(props).filter(([key]) => !key.startsWith("$")))
393
+ } : void 0;
394
+ if (hasTemplate) return convertTemplate(node, templateChildren, dataSource, context, depth, visited, retained, component === "List" ? mapped : void 0);
395
+ if (!reserveNode(context)) return null;
396
+ const converted = mapped ?? retained;
397
+ if (!converted) return null;
290
398
  const children = childrenOf(node, dataSource, context, depth, visited);
399
+ if (mapped?.$type === "Button" && mapped["label"] === void 0) {
400
+ const label = textLabel(node, children, context);
401
+ if (label !== void 0) return {
402
+ ...mapped,
403
+ label
404
+ };
405
+ }
406
+ const listChildren = component === "List" && mapped?.$type === "ListView" ? children.map((child) => ({
407
+ $type: "ListViewItem",
408
+ children: child
409
+ })) : children;
291
410
  return {
292
- ...mapped,
293
- ...children.length > 0 ? { children } : {}
411
+ ...converted,
412
+ ...listChildren.length > 0 ? { children: listChildren } : {}
294
413
  };
295
414
  } finally {
296
415
  visited.delete(componentId);
297
416
  }
298
417
  }
299
- function convertSurfaceToUISpec(surface) {
418
+ function convertSurfaceToUISpec(surface, options = {}) {
300
419
  const warnings = [];
301
420
  if (!surface.components.has("root")) return {
302
421
  spec: null,
@@ -309,7 +428,8 @@ function convertSurfaceToUISpec(surface) {
309
428
  emittedNodes: 0,
310
429
  depthWarned: false,
311
430
  budgetWarned: false,
312
- templateCapWarned: false
431
+ templateCapWarned: false,
432
+ keepUnknownComponents: options.keepUnknownComponents === true
313
433
  };
314
434
  try {
315
435
  return {