@genkit-ai/a2ui 0.5.0-rc.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 (51) hide show
  1. package/LICENSE +202 -0
  2. package/README.md +314 -0
  3. package/lib/catalog.d.mts +70 -0
  4. package/lib/catalog.d.ts +70 -0
  5. package/lib/catalog.js +285 -0
  6. package/lib/catalog.js.map +1 -0
  7. package/lib/catalog.mjs +256 -0
  8. package/lib/catalog.mjs.map +1 -0
  9. package/lib/client.d.mts +59 -0
  10. package/lib/client.d.ts +59 -0
  11. package/lib/client.js +82 -0
  12. package/lib/client.js.map +1 -0
  13. package/lib/client.mjs +55 -0
  14. package/lib/client.mjs.map +1 -0
  15. package/lib/index.d.mts +8 -0
  16. package/lib/index.d.ts +8 -0
  17. package/lib/index.js +62 -0
  18. package/lib/index.js.map +1 -0
  19. package/lib/index.mjs +36 -0
  20. package/lib/index.mjs.map +1 -0
  21. package/lib/loader.d.mts +70 -0
  22. package/lib/loader.d.ts +70 -0
  23. package/lib/loader.js +92 -0
  24. package/lib/loader.js.map +1 -0
  25. package/lib/loader.mjs +61 -0
  26. package/lib/loader.mjs.map +1 -0
  27. package/lib/middleware.d.mts +112 -0
  28. package/lib/middleware.d.ts +112 -0
  29. package/lib/middleware.js +259 -0
  30. package/lib/middleware.js.map +1 -0
  31. package/lib/middleware.mjs +240 -0
  32. package/lib/middleware.mjs.map +1 -0
  33. package/lib/parser.d.mts +132 -0
  34. package/lib/parser.d.ts +132 -0
  35. package/lib/parser.js +282 -0
  36. package/lib/parser.js.map +1 -0
  37. package/lib/parser.mjs +260 -0
  38. package/lib/parser.mjs.map +1 -0
  39. package/lib/part.d.mts +56 -0
  40. package/lib/part.d.ts +56 -0
  41. package/lib/part.js +52 -0
  42. package/lib/part.js.map +1 -0
  43. package/lib/part.mjs +26 -0
  44. package/lib/part.mjs.map +1 -0
  45. package/lib/types.d.mts +153 -0
  46. package/lib/types.d.ts +153 -0
  47. package/lib/types.js +38 -0
  48. package/lib/types.js.map +1 -0
  49. package/lib/types.mjs +11 -0
  50. package/lib/types.mjs.map +1 -0
  51. package/package.json +80 -0
package/lib/catalog.js ADDED
@@ -0,0 +1,285 @@
1
+ "use strict";
2
+ var __defProp = Object.defineProperty;
3
+ var __getOwnPropDesc = Object.getOwnPropertyDescriptor;
4
+ var __getOwnPropNames = Object.getOwnPropertyNames;
5
+ var __hasOwnProp = Object.prototype.hasOwnProperty;
6
+ var __export = (target, all) => {
7
+ for (var name in all)
8
+ __defProp(target, name, { get: all[name], enumerable: true });
9
+ };
10
+ var __copyProps = (to, from, except, desc) => {
11
+ if (from && typeof from === "object" || typeof from === "function") {
12
+ for (let key of __getOwnPropNames(from))
13
+ if (!__hasOwnProp.call(to, key) && key !== except)
14
+ __defProp(to, key, { get: () => from[key], enumerable: !(desc = __getOwnPropDesc(from, key)) || desc.enumerable });
15
+ }
16
+ return to;
17
+ };
18
+ var __toCommonJS = (mod) => __copyProps(__defProp({}, "__esModule", { value: true }), mod);
19
+ var catalog_exports = {};
20
+ __export(catalog_exports, {
21
+ A2UI_CATALOG_VALUE_TYPE: () => A2UI_CATALOG_VALUE_TYPE,
22
+ BASIC_ICON_NAMES: () => BASIC_ICON_NAMES,
23
+ DEFAULT_CATALOG_ID: () => DEFAULT_CATALOG_ID,
24
+ SURFACE_ID_PLACEHOLDER: () => SURFACE_ID_PLACEHOLDER,
25
+ basicCatalog: () => basicCatalog,
26
+ renderCatalogInstructions: () => renderCatalogInstructions
27
+ });
28
+ module.exports = __toCommonJS(catalog_exports);
29
+ var import_types = require("./types.js");
30
+ const A2UI_CATALOG_VALUE_TYPE = "a2ui-catalog";
31
+ const DEFAULT_CATALOG_ID = "basic";
32
+ const BASIC_ICON_NAMES = [
33
+ "accountCircle",
34
+ "add",
35
+ "arrowBack",
36
+ "arrowForward",
37
+ "attachFile",
38
+ "calendarToday",
39
+ "call",
40
+ "camera",
41
+ "check",
42
+ "close",
43
+ "delete",
44
+ "download",
45
+ "edit",
46
+ "event",
47
+ "error",
48
+ "fastForward",
49
+ "favorite",
50
+ "favoriteOff",
51
+ "folder",
52
+ "help",
53
+ "home",
54
+ "info",
55
+ "locationOn",
56
+ "lock",
57
+ "lockOpen",
58
+ "mail",
59
+ "menu",
60
+ "moreVert",
61
+ "moreHoriz",
62
+ "notificationsOff",
63
+ "notifications",
64
+ "pause",
65
+ "payment",
66
+ "person",
67
+ "phone",
68
+ "photo",
69
+ "play",
70
+ "print",
71
+ "refresh",
72
+ "rewind",
73
+ "search",
74
+ "send",
75
+ "settings",
76
+ "share",
77
+ "shoppingCart",
78
+ "skipNext",
79
+ "skipPrevious",
80
+ "star",
81
+ "starHalf",
82
+ "starOff",
83
+ "stop",
84
+ "upload",
85
+ "visibility",
86
+ "visibilityOff",
87
+ "volumeDown",
88
+ "volumeMute",
89
+ "volumeOff",
90
+ "volumeUp",
91
+ "warning"
92
+ ];
93
+ const basicCatalog = {
94
+ id: import_types.BASIC_CATALOG_ID,
95
+ components: [
96
+ {
97
+ name: "Text",
98
+ description: "Displays a run of text. For headings/titles set the `variant` prop (h1..h5) rather than embedding Markdown; the text itself may use inline Markdown.",
99
+ props: "text: string (required); variant?: one of h1|h2|h3|h4|h5|caption|body."
100
+ },
101
+ {
102
+ name: "Image",
103
+ description: "Displays an image from a URL.",
104
+ props: "url: string (required); description?: string; fit?: contain|cover|fill|none|scaleDown; variant?: icon|avatar|smallFeature|mediumFeature|largeFeature|header."
105
+ },
106
+ {
107
+ name: "Icon",
108
+ description: 'Displays a named material icon. `name` MUST be one of the exact names listed below \u2014 do NOT invent names (e.g. there is no "cloud", "air", or "thermostat"). If none fits, omit the Icon rather than guessing.',
109
+ props: `name: one of ${BASIC_ICON_NAMES.join(", ")} (required, exact).`
110
+ },
111
+ {
112
+ name: "Row",
113
+ description: "Lays out children horizontally.",
114
+ props: "children: string[] of component ids (required); justify?: start|center|end|spaceAround|spaceBetween|spaceEvenly|stretch; align?: start|center|end|stretch."
115
+ },
116
+ {
117
+ name: "Column",
118
+ description: "Lays out children vertically.",
119
+ props: "children: string[] of component ids (required); justify?: start|center|end|spaceBetween|spaceAround|spaceEvenly|stretch; align?: start|center|end|stretch."
120
+ },
121
+ {
122
+ name: "List",
123
+ description: "A list of children.",
124
+ props: "children: string[] of component ids (required); direction?: vertical|horizontal; listStyle?: ordered|unordered|none."
125
+ },
126
+ {
127
+ name: "Card",
128
+ description: "A visually-contained card wrapping a single child.",
129
+ props: "child: string id of a single child component (required; wrap multiple in a Column/Row)."
130
+ },
131
+ {
132
+ name: "Divider",
133
+ description: "A horizontal or vertical separator line.",
134
+ props: "axis?: horizontal|vertical."
135
+ },
136
+ {
137
+ name: "Button",
138
+ description: "A clickable button that fires an action back to the agent.",
139
+ props: "child: string id of a child (usually a Text) (required); variant?: default|primary|borderless; action: { event: { name: string, context?: object } } (required \u2014 the event name is sent back to the agent when clicked)."
140
+ },
141
+ {
142
+ name: "TextField",
143
+ description: "A single- or multi-line text input.",
144
+ props: "label: string (required); value?: string or { path } binding; variant?: shortText|longText|number|obscured."
145
+ },
146
+ {
147
+ name: "CheckBox",
148
+ description: "A labeled checkbox.",
149
+ props: "label: string (required); value: boolean or { path } binding (required)."
150
+ },
151
+ {
152
+ name: "Slider",
153
+ description: "A numeric slider.",
154
+ props: "max: number (required); value: number or { path } binding (required); label?: string; min?: number; step?: number."
155
+ }
156
+ ]
157
+ };
158
+ function renderStyleTips(has) {
159
+ const tips = [];
160
+ const containers = ["Card", "Column", "Row"].filter((c) => has.has(c));
161
+ if (containers.length) {
162
+ tips.push(
163
+ `- Group related content with layout components (${containers.join(
164
+ "/"
165
+ )}) and give it a clear hierarchy.`
166
+ );
167
+ }
168
+ if (has.has("Text")) {
169
+ tips.push(
170
+ '- Give titles a heading `variant` (e.g. h2/h3) and secondary text the `caption` variant instead of embedding "#"/"##" heading markers in the text.'
171
+ );
172
+ }
173
+ const accents = ["Icon", "Divider", "Image"].filter((c) => has.has(c));
174
+ if (accents.length) {
175
+ tips.push(
176
+ `- Use ${accents.join(
177
+ "/"
178
+ )} to add visual meaning and separate sections where it helps.`
179
+ );
180
+ }
181
+ if (has.has("Button")) {
182
+ tips.push('- Give primary buttons `variant: "primary"`.');
183
+ }
184
+ return tips.length ? `
185
+
186
+ Make it look good, not bland:
187
+ ${tips.join("\n")}` : "";
188
+ }
189
+ function renderExample(catalog, has) {
190
+ if (has.has("Card") && has.has("Column") && has.has("Text")) {
191
+ return `
192
+
193
+ Example (a small weather card):
194
+ \`\`\`a2ui
195
+ [
196
+ { "createSurface": { "surfaceId": "SURFACE_ID", "catalogId": "${catalog.id}" } },
197
+ { "updateComponents": { "surfaceId": "SURFACE_ID", "components": [
198
+ { "id": "root", "component": "Card", "child": "body" },
199
+ { "id": "body", "component": "Column", "children": ["title", "temp"] },
200
+ { "id": "title", "component": "Text", "text": "Weather in Tokyo", "variant": "h3" },
201
+ { "id": "temp", "component": "Text", "text": { "path": "/temp" } }
202
+ ] } },
203
+ { "updateDataModel": { "surfaceId": "SURFACE_ID", "path": "/temp", "value": "18\xB0C" } }
204
+ ]
205
+ \`\`\``;
206
+ }
207
+ const rootComponent = catalog.components[0]?.name ?? "Text";
208
+ return `
209
+
210
+ Example (a minimal surface):
211
+ \`\`\`a2ui
212
+ [
213
+ { "createSurface": { "surfaceId": "SURFACE_ID", "catalogId": "${catalog.id}" } },
214
+ { "updateComponents": { "surfaceId": "SURFACE_ID", "components": [
215
+ { "id": "root", "component": "${rootComponent}" }
216
+ ] } }
217
+ ]
218
+ \`\`\``;
219
+ }
220
+ function renderCatalogInstructions(catalog) {
221
+ const componentDocs = catalog.components.map((c) => `- ${c.name}: ${c.description} Props: ${c.props}`).join("\n");
222
+ const has = new Set(catalog.components.map((c) => c.name));
223
+ const styleSection = renderStyleTips(has);
224
+ const exampleSection = renderExample(catalog, has);
225
+ const inputs = ["TextField", "CheckBox", "Slider"].filter((c) => has.has(c));
226
+ const inputList = inputs.join(", ");
227
+ const formsSection = inputs.length ? `
228
+ - Forms: input components (${inputList}) do NOT send their values automatically.
229
+ To capture what the user entered you MUST do BOTH of these:
230
+ 1. Bind each input's \`value\` to a data-model path, e.g.
231
+ \`{ "component": "TextField", "label": "Email", "value": { "path": "/email" } }\`.
232
+ Typing updates the data model at that path.
233
+ 2. On the submit \`Button\`, echo those same paths in
234
+ \`action.event.context\` so their current values are sent back to you, e.g.
235
+ \`"context": { "email": { "path": "/email" }, "name": { "path": "/name" } }\`.
236
+ Without the \`{ path }\` bindings and the button \`context\`, the action arrives
237
+ with an empty \`context\` and the entered values are lost.` : "";
238
+ return `# Rendering UI with A2UI
239
+
240
+ You can render rich, interactive UI (not just text) by emitting an A2UI surface.
241
+ When a result is better *shown* than *told* (weather, lists, forms, comparisons,
242
+ confirmations, anything visual or interactive), render a UI surface.
243
+
244
+ To render UI, output a single fenced code block tagged \`a2ui\` containing a JSON
245
+ array of A2UI envelope messages. You may still write normal prose before it.
246
+
247
+ Rules:
248
+ - The UI is an ADJACENCY LIST: a flat array of components. Build the tree using
249
+ string \`id\` references, NOT nested objects. Exactly one component MUST have
250
+ \`id: "root"\`.
251
+ - Every component has a \`component\` (type name) and an \`id\`. Container
252
+ components reference their children by id via a \`children\` array; single-child
253
+ wrappers reference one \`child\` id.
254
+ - Values can be literals, or a data-model binding \`{ "path": "/somePath" }\`.
255
+ - Use \`createSurface\` first (with \`catalogId\`), then \`updateComponents\` to add
256
+ the component list, then optionally \`updateDataModel\` to set data. You may
257
+ combine them in one array, in order.
258
+ - Interactive components fire an \`action\` with an event \`name\`; that name is
259
+ sent back to you when the user interacts, so choose meaningful names.${formsSection}
260
+ - When a user interacts with a surface (e.g. presses a button) and you respond
261
+ with updated UI, RE-RENDER THE WHOLE SURFACE: start again with
262
+ \`createSurface\` followed by \`updateComponents\`. Do not emit a bare
263
+ \`updateDataModel\`/\`updateComponents\` expecting a previous surface to still
264
+ exist.${styleSection}
265
+
266
+ The catalogId to use is:
267
+ "${catalog.id}"
268
+
269
+ Available components:
270
+ ${componentDocs}${exampleSection}
271
+
272
+ Do not explain the JSON; just render the block. Use "SURFACE_ID" literally as a
273
+ placeholder for the surface id \u2014 the system replaces it with a real id.`;
274
+ }
275
+ const SURFACE_ID_PLACEHOLDER = "SURFACE_ID";
276
+ // Annotate the CommonJS export names for ESM import in node:
277
+ 0 && (module.exports = {
278
+ A2UI_CATALOG_VALUE_TYPE,
279
+ BASIC_ICON_NAMES,
280
+ DEFAULT_CATALOG_ID,
281
+ SURFACE_ID_PLACEHOLDER,
282
+ basicCatalog,
283
+ renderCatalogInstructions
284
+ });
285
+ //# sourceMappingURL=catalog.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"sources":["../src/catalog.ts"],"sourcesContent":["/**\n * Copyright 2026 Google LLC\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n * http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\n/**\n * A2UI catalog description used by the {@link a2ui} middleware.\n *\n * A catalog pins the set of components a surface may render. The middleware uses\n * it for two things: (1) telling the model what it may render (prompt\n * injection), and (2) validating emitted envelopes only reference known\n * components. The renderer on the client registers a matching `@a2ui/*` catalog\n * under the same `id`.\n *\n * @module\n */\n\nimport { BASIC_CATALOG_ID } from './types.js';\n\n/**\n * The registry value type under which A2UI catalogs are stored, so the\n * {@link a2ui} middleware can look them up by id (and, in the future, tooling\n * can list them). Register with `ai.registry.registerValue(...)` via\n * {@link loadCatalog}.\n */\nexport const A2UI_CATALOG_VALUE_TYPE = 'a2ui-catalog';\n\n/**\n * The default catalog id used by the {@link a2ui} middleware when none is\n * given. Resolves to the bundled {@link basicCatalog}.\n */\nexport const DEFAULT_CATALOG_ID = 'basic';\n\n/** A component the model may use, plus a short description of its props. */\nexport interface A2uiCatalogComponent {\n /** The component type name, e.g. `Text`. */\n name: string;\n /** One-line description of what the component renders. */\n description: string;\n /**\n * A short, model-facing description of the component's props. Kept as plain\n * text (rather than a JSON Schema) to keep the injected prompt compact.\n */\n props: string;\n}\n\n/** A parsed catalog: an id plus the components it exposes. */\nexport interface A2uiCatalog {\n /** Globally-unique catalog id (also used as `catalogId` on `createSurface`). */\n id: string;\n /** The components available in this catalog. */\n components: A2uiCatalogComponent[];\n}\n\n/**\n * The set of icon names the basic catalog's `Icon` component supports. Names\n * outside this list render as literal text (the renderer degrades gracefully),\n * so the prompt lists them to steer the model toward valid names. Note the\n * middleware validates component *types* against the catalog but does not\n * validate individual `Icon` name values.\n */\nexport const BASIC_ICON_NAMES = [\n 'accountCircle',\n 'add',\n 'arrowBack',\n 'arrowForward',\n 'attachFile',\n 'calendarToday',\n 'call',\n 'camera',\n 'check',\n 'close',\n 'delete',\n 'download',\n 'edit',\n 'event',\n 'error',\n 'fastForward',\n 'favorite',\n 'favoriteOff',\n 'folder',\n 'help',\n 'home',\n 'info',\n 'locationOn',\n 'lock',\n 'lockOpen',\n 'mail',\n 'menu',\n 'moreVert',\n 'moreHoriz',\n 'notificationsOff',\n 'notifications',\n 'pause',\n 'payment',\n 'person',\n 'phone',\n 'photo',\n 'play',\n 'print',\n 'refresh',\n 'rewind',\n 'search',\n 'send',\n 'settings',\n 'share',\n 'shoppingCart',\n 'skipNext',\n 'skipPrevious',\n 'star',\n 'starHalf',\n 'starOff',\n 'stop',\n 'upload',\n 'visibility',\n 'visibilityOff',\n 'volumeDown',\n 'volumeMute',\n 'volumeOff',\n 'volumeUp',\n 'warning',\n] as const;\n\n/**\n * The A2UI \"Basic Catalog\" (v0.9), mirroring the components published by\n * `@a2ui/web_core`'s basic catalog. Use this to render standard UI without\n * defining your own design system.\n */\nexport const basicCatalog: A2uiCatalog = {\n id: BASIC_CATALOG_ID,\n components: [\n {\n name: 'Text',\n description:\n 'Displays a run of text. For headings/titles set the `variant` prop ' +\n '(h1..h5) rather than embedding Markdown; the text itself may use ' +\n 'inline Markdown.',\n props:\n 'text: string (required); variant?: one of h1|h2|h3|h4|h5|caption|body.',\n },\n {\n name: 'Image',\n description: 'Displays an image from a URL.',\n props:\n 'url: string (required); description?: string; fit?: contain|cover|fill|none|scaleDown; variant?: icon|avatar|smallFeature|mediumFeature|largeFeature|header.',\n },\n {\n name: 'Icon',\n description:\n 'Displays a named material icon. `name` MUST be one of the exact names ' +\n 'listed below — do NOT invent names (e.g. there is no \"cloud\", \"air\", ' +\n 'or \"thermostat\"). If none fits, omit the Icon rather than guessing.',\n props: `name: one of ${BASIC_ICON_NAMES.join(', ')} (required, exact).`,\n },\n {\n name: 'Row',\n description: 'Lays out children horizontally.',\n props:\n 'children: string[] of component ids (required); justify?: start|center|end|spaceAround|spaceBetween|spaceEvenly|stretch; align?: start|center|end|stretch.',\n },\n {\n name: 'Column',\n description: 'Lays out children vertically.',\n props:\n 'children: string[] of component ids (required); justify?: start|center|end|spaceBetween|spaceAround|spaceEvenly|stretch; align?: start|center|end|stretch.',\n },\n {\n name: 'List',\n description: 'A list of children.',\n props:\n 'children: string[] of component ids (required); direction?: vertical|horizontal; listStyle?: ordered|unordered|none.',\n },\n {\n name: 'Card',\n description: 'A visually-contained card wrapping a single child.',\n props:\n 'child: string id of a single child component (required; wrap multiple in a Column/Row).',\n },\n {\n name: 'Divider',\n description: 'A horizontal or vertical separator line.',\n props: 'axis?: horizontal|vertical.',\n },\n {\n name: 'Button',\n description: 'A clickable button that fires an action back to the agent.',\n props:\n 'child: string id of a child (usually a Text) (required); variant?: default|primary|borderless; action: { event: { name: string, context?: object } } (required — the event name is sent back to the agent when clicked).',\n },\n {\n name: 'TextField',\n description: 'A single- or multi-line text input.',\n props:\n 'label: string (required); value?: string or { path } binding; variant?: shortText|longText|number|obscured.',\n },\n {\n name: 'CheckBox',\n description: 'A labeled checkbox.',\n props:\n 'label: string (required); value: boolean or { path } binding (required).',\n },\n {\n name: 'Slider',\n description: 'A numeric slider.',\n props:\n 'max: number (required); value: number or { path } binding (required); label?: string; min?: number; step?: number.',\n },\n ],\n};\n\n/**\n * Builds the \"make it look good\" styling tips, scoped to the components the\n * catalog actually provides so a custom catalog is never told to emit\n * components it lacks (which would then fail `validate: 'strict'`).\n */\nfunction renderStyleTips(has: Set<string>): string {\n const tips: string[] = [];\n const containers = ['Card', 'Column', 'Row'].filter((c) => has.has(c));\n if (containers.length) {\n tips.push(\n `- Group related content with layout components (${containers.join(\n '/'\n )}) and give it a clear hierarchy.`\n );\n }\n if (has.has('Text')) {\n tips.push(\n '- Give titles a heading `variant` (e.g. h2/h3) and secondary text the ' +\n '`caption` variant instead of embedding \"#\"/\"##\" heading markers in ' +\n 'the text.'\n );\n }\n const accents = ['Icon', 'Divider', 'Image'].filter((c) => has.has(c));\n if (accents.length) {\n tips.push(\n `- Use ${accents.join(\n '/'\n )} to add visual meaning and separate sections where it helps.`\n );\n }\n if (has.has('Button')) {\n tips.push('- Give primary buttons `variant: \"primary\"`.');\n }\n return tips.length\n ? `\\n\\nMake it look good, not bland:\\n${tips.join('\\n')}`\n : '';\n}\n\n/**\n * Builds a worked example. Uses a rich Card/Column/Text layout when the catalog\n * supports it (the common case, e.g. the basic catalog); otherwise falls back\n * to a minimal example built only from components the catalog provides, so the\n * example never references unknown components.\n */\nfunction renderExample(catalog: A2uiCatalog, has: Set<string>): string {\n if (has.has('Card') && has.has('Column') && has.has('Text')) {\n return `\n\nExample (a small weather card):\n\\`\\`\\`a2ui\n[\n { \"createSurface\": { \"surfaceId\": \"SURFACE_ID\", \"catalogId\": \"${catalog.id}\" } },\n { \"updateComponents\": { \"surfaceId\": \"SURFACE_ID\", \"components\": [\n { \"id\": \"root\", \"component\": \"Card\", \"child\": \"body\" },\n { \"id\": \"body\", \"component\": \"Column\", \"children\": [\"title\", \"temp\"] },\n { \"id\": \"title\", \"component\": \"Text\", \"text\": \"Weather in Tokyo\", \"variant\": \"h3\" },\n { \"id\": \"temp\", \"component\": \"Text\", \"text\": { \"path\": \"/temp\" } }\n ] } },\n { \"updateDataModel\": { \"surfaceId\": \"SURFACE_ID\", \"path\": \"/temp\", \"value\": \"18°C\" } }\n]\n\\`\\`\\``;\n }\n // Minimal fallback: root uses whatever the catalog's first component is.\n const rootComponent = catalog.components[0]?.name ?? 'Text';\n return `\n\nExample (a minimal surface):\n\\`\\`\\`a2ui\n[\n { \"createSurface\": { \"surfaceId\": \"SURFACE_ID\", \"catalogId\": \"${catalog.id}\" } },\n { \"updateComponents\": { \"surfaceId\": \"SURFACE_ID\", \"components\": [\n { \"id\": \"root\", \"component\": \"${rootComponent}\" }\n ] } }\n]\n\\`\\`\\``;\n}\n\n/**\n * Renders a catalog into model-facing instructions describing the A2UI protocol\n * and the available components. Injected into the system prompt by the\n * middleware when `instructions !== 'none'`.\n */\nexport function renderCatalogInstructions(catalog: A2uiCatalog): string {\n const componentDocs = catalog.components\n .map((c) => `- ${c.name}: ${c.description} Props: ${c.props}`)\n .join('\\n');\n\n const has = new Set(catalog.components.map((c) => c.name));\n const styleSection = renderStyleTips(has);\n const exampleSection = renderExample(catalog, has);\n\n // Forms guidance only applies if the catalog has input components.\n const inputs = ['TextField', 'CheckBox', 'Slider'].filter((c) => has.has(c));\n const inputList = inputs.join(', ');\n const formsSection = inputs.length\n ? `\n- Forms: input components (${inputList}) do NOT send their values automatically.\n To capture what the user entered you MUST do BOTH of these:\n 1. Bind each input's \\`value\\` to a data-model path, e.g.\n \\`{ \"component\": \"TextField\", \"label\": \"Email\", \"value\": { \"path\": \"/email\" } }\\`.\n Typing updates the data model at that path.\n 2. On the submit \\`Button\\`, echo those same paths in\n \\`action.event.context\\` so their current values are sent back to you, e.g.\n \\`\"context\": { \"email\": { \"path\": \"/email\" }, \"name\": { \"path\": \"/name\" } }\\`.\n Without the \\`{ path }\\` bindings and the button \\`context\\`, the action arrives\n with an empty \\`context\\` and the entered values are lost.`\n : '';\n\n return `# Rendering UI with A2UI\n\nYou can render rich, interactive UI (not just text) by emitting an A2UI surface.\nWhen a result is better *shown* than *told* (weather, lists, forms, comparisons,\nconfirmations, anything visual or interactive), render a UI surface.\n\nTo render UI, output a single fenced code block tagged \\`a2ui\\` containing a JSON\narray of A2UI envelope messages. You may still write normal prose before it.\n\nRules:\n- The UI is an ADJACENCY LIST: a flat array of components. Build the tree using\n string \\`id\\` references, NOT nested objects. Exactly one component MUST have\n \\`id: \"root\"\\`.\n- Every component has a \\`component\\` (type name) and an \\`id\\`. Container\n components reference their children by id via a \\`children\\` array; single-child\n wrappers reference one \\`child\\` id.\n- Values can be literals, or a data-model binding \\`{ \"path\": \"/somePath\" }\\`.\n- Use \\`createSurface\\` first (with \\`catalogId\\`), then \\`updateComponents\\` to add\n the component list, then optionally \\`updateDataModel\\` to set data. You may\n combine them in one array, in order.\n- Interactive components fire an \\`action\\` with an event \\`name\\`; that name is\n sent back to you when the user interacts, so choose meaningful names.${formsSection}\n- When a user interacts with a surface (e.g. presses a button) and you respond\n with updated UI, RE-RENDER THE WHOLE SURFACE: start again with\n \\`createSurface\\` followed by \\`updateComponents\\`. Do not emit a bare\n \\`updateDataModel\\`/\\`updateComponents\\` expecting a previous surface to still\n exist.${styleSection}\n\nThe catalogId to use is:\n\"${catalog.id}\"\n\nAvailable components:\n${componentDocs}${exampleSection}\n\nDo not explain the JSON; just render the block. Use \"SURFACE_ID\" literally as a\nplaceholder for the surface id — the system replaces it with a real id.`;\n}\n\n/** The literal placeholder the model is told to use for surface ids. */\nexport const SURFACE_ID_PLACEHOLDER = 'SURFACE_ID';\n"],"mappings":"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","names":[]}
@@ -0,0 +1,256 @@
1
+ import { BASIC_CATALOG_ID } from "./types.mjs";
2
+ const A2UI_CATALOG_VALUE_TYPE = "a2ui-catalog";
3
+ const DEFAULT_CATALOG_ID = "basic";
4
+ const BASIC_ICON_NAMES = [
5
+ "accountCircle",
6
+ "add",
7
+ "arrowBack",
8
+ "arrowForward",
9
+ "attachFile",
10
+ "calendarToday",
11
+ "call",
12
+ "camera",
13
+ "check",
14
+ "close",
15
+ "delete",
16
+ "download",
17
+ "edit",
18
+ "event",
19
+ "error",
20
+ "fastForward",
21
+ "favorite",
22
+ "favoriteOff",
23
+ "folder",
24
+ "help",
25
+ "home",
26
+ "info",
27
+ "locationOn",
28
+ "lock",
29
+ "lockOpen",
30
+ "mail",
31
+ "menu",
32
+ "moreVert",
33
+ "moreHoriz",
34
+ "notificationsOff",
35
+ "notifications",
36
+ "pause",
37
+ "payment",
38
+ "person",
39
+ "phone",
40
+ "photo",
41
+ "play",
42
+ "print",
43
+ "refresh",
44
+ "rewind",
45
+ "search",
46
+ "send",
47
+ "settings",
48
+ "share",
49
+ "shoppingCart",
50
+ "skipNext",
51
+ "skipPrevious",
52
+ "star",
53
+ "starHalf",
54
+ "starOff",
55
+ "stop",
56
+ "upload",
57
+ "visibility",
58
+ "visibilityOff",
59
+ "volumeDown",
60
+ "volumeMute",
61
+ "volumeOff",
62
+ "volumeUp",
63
+ "warning"
64
+ ];
65
+ const basicCatalog = {
66
+ id: BASIC_CATALOG_ID,
67
+ components: [
68
+ {
69
+ name: "Text",
70
+ description: "Displays a run of text. For headings/titles set the `variant` prop (h1..h5) rather than embedding Markdown; the text itself may use inline Markdown.",
71
+ props: "text: string (required); variant?: one of h1|h2|h3|h4|h5|caption|body."
72
+ },
73
+ {
74
+ name: "Image",
75
+ description: "Displays an image from a URL.",
76
+ props: "url: string (required); description?: string; fit?: contain|cover|fill|none|scaleDown; variant?: icon|avatar|smallFeature|mediumFeature|largeFeature|header."
77
+ },
78
+ {
79
+ name: "Icon",
80
+ description: 'Displays a named material icon. `name` MUST be one of the exact names listed below \u2014 do NOT invent names (e.g. there is no "cloud", "air", or "thermostat"). If none fits, omit the Icon rather than guessing.',
81
+ props: `name: one of ${BASIC_ICON_NAMES.join(", ")} (required, exact).`
82
+ },
83
+ {
84
+ name: "Row",
85
+ description: "Lays out children horizontally.",
86
+ props: "children: string[] of component ids (required); justify?: start|center|end|spaceAround|spaceBetween|spaceEvenly|stretch; align?: start|center|end|stretch."
87
+ },
88
+ {
89
+ name: "Column",
90
+ description: "Lays out children vertically.",
91
+ props: "children: string[] of component ids (required); justify?: start|center|end|spaceBetween|spaceAround|spaceEvenly|stretch; align?: start|center|end|stretch."
92
+ },
93
+ {
94
+ name: "List",
95
+ description: "A list of children.",
96
+ props: "children: string[] of component ids (required); direction?: vertical|horizontal; listStyle?: ordered|unordered|none."
97
+ },
98
+ {
99
+ name: "Card",
100
+ description: "A visually-contained card wrapping a single child.",
101
+ props: "child: string id of a single child component (required; wrap multiple in a Column/Row)."
102
+ },
103
+ {
104
+ name: "Divider",
105
+ description: "A horizontal or vertical separator line.",
106
+ props: "axis?: horizontal|vertical."
107
+ },
108
+ {
109
+ name: "Button",
110
+ description: "A clickable button that fires an action back to the agent.",
111
+ props: "child: string id of a child (usually a Text) (required); variant?: default|primary|borderless; action: { event: { name: string, context?: object } } (required \u2014 the event name is sent back to the agent when clicked)."
112
+ },
113
+ {
114
+ name: "TextField",
115
+ description: "A single- or multi-line text input.",
116
+ props: "label: string (required); value?: string or { path } binding; variant?: shortText|longText|number|obscured."
117
+ },
118
+ {
119
+ name: "CheckBox",
120
+ description: "A labeled checkbox.",
121
+ props: "label: string (required); value: boolean or { path } binding (required)."
122
+ },
123
+ {
124
+ name: "Slider",
125
+ description: "A numeric slider.",
126
+ props: "max: number (required); value: number or { path } binding (required); label?: string; min?: number; step?: number."
127
+ }
128
+ ]
129
+ };
130
+ function renderStyleTips(has) {
131
+ const tips = [];
132
+ const containers = ["Card", "Column", "Row"].filter((c) => has.has(c));
133
+ if (containers.length) {
134
+ tips.push(
135
+ `- Group related content with layout components (${containers.join(
136
+ "/"
137
+ )}) and give it a clear hierarchy.`
138
+ );
139
+ }
140
+ if (has.has("Text")) {
141
+ tips.push(
142
+ '- Give titles a heading `variant` (e.g. h2/h3) and secondary text the `caption` variant instead of embedding "#"/"##" heading markers in the text.'
143
+ );
144
+ }
145
+ const accents = ["Icon", "Divider", "Image"].filter((c) => has.has(c));
146
+ if (accents.length) {
147
+ tips.push(
148
+ `- Use ${accents.join(
149
+ "/"
150
+ )} to add visual meaning and separate sections where it helps.`
151
+ );
152
+ }
153
+ if (has.has("Button")) {
154
+ tips.push('- Give primary buttons `variant: "primary"`.');
155
+ }
156
+ return tips.length ? `
157
+
158
+ Make it look good, not bland:
159
+ ${tips.join("\n")}` : "";
160
+ }
161
+ function renderExample(catalog, has) {
162
+ if (has.has("Card") && has.has("Column") && has.has("Text")) {
163
+ return `
164
+
165
+ Example (a small weather card):
166
+ \`\`\`a2ui
167
+ [
168
+ { "createSurface": { "surfaceId": "SURFACE_ID", "catalogId": "${catalog.id}" } },
169
+ { "updateComponents": { "surfaceId": "SURFACE_ID", "components": [
170
+ { "id": "root", "component": "Card", "child": "body" },
171
+ { "id": "body", "component": "Column", "children": ["title", "temp"] },
172
+ { "id": "title", "component": "Text", "text": "Weather in Tokyo", "variant": "h3" },
173
+ { "id": "temp", "component": "Text", "text": { "path": "/temp" } }
174
+ ] } },
175
+ { "updateDataModel": { "surfaceId": "SURFACE_ID", "path": "/temp", "value": "18\xB0C" } }
176
+ ]
177
+ \`\`\``;
178
+ }
179
+ const rootComponent = catalog.components[0]?.name ?? "Text";
180
+ return `
181
+
182
+ Example (a minimal surface):
183
+ \`\`\`a2ui
184
+ [
185
+ { "createSurface": { "surfaceId": "SURFACE_ID", "catalogId": "${catalog.id}" } },
186
+ { "updateComponents": { "surfaceId": "SURFACE_ID", "components": [
187
+ { "id": "root", "component": "${rootComponent}" }
188
+ ] } }
189
+ ]
190
+ \`\`\``;
191
+ }
192
+ function renderCatalogInstructions(catalog) {
193
+ const componentDocs = catalog.components.map((c) => `- ${c.name}: ${c.description} Props: ${c.props}`).join("\n");
194
+ const has = new Set(catalog.components.map((c) => c.name));
195
+ const styleSection = renderStyleTips(has);
196
+ const exampleSection = renderExample(catalog, has);
197
+ const inputs = ["TextField", "CheckBox", "Slider"].filter((c) => has.has(c));
198
+ const inputList = inputs.join(", ");
199
+ const formsSection = inputs.length ? `
200
+ - Forms: input components (${inputList}) do NOT send their values automatically.
201
+ To capture what the user entered you MUST do BOTH of these:
202
+ 1. Bind each input's \`value\` to a data-model path, e.g.
203
+ \`{ "component": "TextField", "label": "Email", "value": { "path": "/email" } }\`.
204
+ Typing updates the data model at that path.
205
+ 2. On the submit \`Button\`, echo those same paths in
206
+ \`action.event.context\` so their current values are sent back to you, e.g.
207
+ \`"context": { "email": { "path": "/email" }, "name": { "path": "/name" } }\`.
208
+ Without the \`{ path }\` bindings and the button \`context\`, the action arrives
209
+ with an empty \`context\` and the entered values are lost.` : "";
210
+ return `# Rendering UI with A2UI
211
+
212
+ You can render rich, interactive UI (not just text) by emitting an A2UI surface.
213
+ When a result is better *shown* than *told* (weather, lists, forms, comparisons,
214
+ confirmations, anything visual or interactive), render a UI surface.
215
+
216
+ To render UI, output a single fenced code block tagged \`a2ui\` containing a JSON
217
+ array of A2UI envelope messages. You may still write normal prose before it.
218
+
219
+ Rules:
220
+ - The UI is an ADJACENCY LIST: a flat array of components. Build the tree using
221
+ string \`id\` references, NOT nested objects. Exactly one component MUST have
222
+ \`id: "root"\`.
223
+ - Every component has a \`component\` (type name) and an \`id\`. Container
224
+ components reference their children by id via a \`children\` array; single-child
225
+ wrappers reference one \`child\` id.
226
+ - Values can be literals, or a data-model binding \`{ "path": "/somePath" }\`.
227
+ - Use \`createSurface\` first (with \`catalogId\`), then \`updateComponents\` to add
228
+ the component list, then optionally \`updateDataModel\` to set data. You may
229
+ combine them in one array, in order.
230
+ - Interactive components fire an \`action\` with an event \`name\`; that name is
231
+ sent back to you when the user interacts, so choose meaningful names.${formsSection}
232
+ - When a user interacts with a surface (e.g. presses a button) and you respond
233
+ with updated UI, RE-RENDER THE WHOLE SURFACE: start again with
234
+ \`createSurface\` followed by \`updateComponents\`. Do not emit a bare
235
+ \`updateDataModel\`/\`updateComponents\` expecting a previous surface to still
236
+ exist.${styleSection}
237
+
238
+ The catalogId to use is:
239
+ "${catalog.id}"
240
+
241
+ Available components:
242
+ ${componentDocs}${exampleSection}
243
+
244
+ Do not explain the JSON; just render the block. Use "SURFACE_ID" literally as a
245
+ placeholder for the surface id \u2014 the system replaces it with a real id.`;
246
+ }
247
+ const SURFACE_ID_PLACEHOLDER = "SURFACE_ID";
248
+ export {
249
+ A2UI_CATALOG_VALUE_TYPE,
250
+ BASIC_ICON_NAMES,
251
+ DEFAULT_CATALOG_ID,
252
+ SURFACE_ID_PLACEHOLDER,
253
+ basicCatalog,
254
+ renderCatalogInstructions
255
+ };
256
+ //# sourceMappingURL=catalog.mjs.map
@@ -0,0 +1 @@
1
+ {"version":3,"sources":["../src/catalog.ts"],"sourcesContent":["/**\n * Copyright 2026 Google LLC\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n * http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\n/**\n * A2UI catalog description used by the {@link a2ui} middleware.\n *\n * A catalog pins the set of components a surface may render. The middleware uses\n * it for two things: (1) telling the model what it may render (prompt\n * injection), and (2) validating emitted envelopes only reference known\n * components. The renderer on the client registers a matching `@a2ui/*` catalog\n * under the same `id`.\n *\n * @module\n */\n\nimport { BASIC_CATALOG_ID } from './types.mjs';\n\n/**\n * The registry value type under which A2UI catalogs are stored, so the\n * {@link a2ui} middleware can look them up by id (and, in the future, tooling\n * can list them). Register with `ai.registry.registerValue(...)` via\n * {@link loadCatalog}.\n */\nexport const A2UI_CATALOG_VALUE_TYPE = 'a2ui-catalog';\n\n/**\n * The default catalog id used by the {@link a2ui} middleware when none is\n * given. Resolves to the bundled {@link basicCatalog}.\n */\nexport const DEFAULT_CATALOG_ID = 'basic';\n\n/** A component the model may use, plus a short description of its props. */\nexport interface A2uiCatalogComponent {\n /** The component type name, e.g. `Text`. */\n name: string;\n /** One-line description of what the component renders. */\n description: string;\n /**\n * A short, model-facing description of the component's props. Kept as plain\n * text (rather than a JSON Schema) to keep the injected prompt compact.\n */\n props: string;\n}\n\n/** A parsed catalog: an id plus the components it exposes. */\nexport interface A2uiCatalog {\n /** Globally-unique catalog id (also used as `catalogId` on `createSurface`). */\n id: string;\n /** The components available in this catalog. */\n components: A2uiCatalogComponent[];\n}\n\n/**\n * The set of icon names the basic catalog's `Icon` component supports. Names\n * outside this list render as literal text (the renderer degrades gracefully),\n * so the prompt lists them to steer the model toward valid names. Note the\n * middleware validates component *types* against the catalog but does not\n * validate individual `Icon` name values.\n */\nexport const BASIC_ICON_NAMES = [\n 'accountCircle',\n 'add',\n 'arrowBack',\n 'arrowForward',\n 'attachFile',\n 'calendarToday',\n 'call',\n 'camera',\n 'check',\n 'close',\n 'delete',\n 'download',\n 'edit',\n 'event',\n 'error',\n 'fastForward',\n 'favorite',\n 'favoriteOff',\n 'folder',\n 'help',\n 'home',\n 'info',\n 'locationOn',\n 'lock',\n 'lockOpen',\n 'mail',\n 'menu',\n 'moreVert',\n 'moreHoriz',\n 'notificationsOff',\n 'notifications',\n 'pause',\n 'payment',\n 'person',\n 'phone',\n 'photo',\n 'play',\n 'print',\n 'refresh',\n 'rewind',\n 'search',\n 'send',\n 'settings',\n 'share',\n 'shoppingCart',\n 'skipNext',\n 'skipPrevious',\n 'star',\n 'starHalf',\n 'starOff',\n 'stop',\n 'upload',\n 'visibility',\n 'visibilityOff',\n 'volumeDown',\n 'volumeMute',\n 'volumeOff',\n 'volumeUp',\n 'warning',\n] as const;\n\n/**\n * The A2UI \"Basic Catalog\" (v0.9), mirroring the components published by\n * `@a2ui/web_core`'s basic catalog. Use this to render standard UI without\n * defining your own design system.\n */\nexport const basicCatalog: A2uiCatalog = {\n id: BASIC_CATALOG_ID,\n components: [\n {\n name: 'Text',\n description:\n 'Displays a run of text. For headings/titles set the `variant` prop ' +\n '(h1..h5) rather than embedding Markdown; the text itself may use ' +\n 'inline Markdown.',\n props:\n 'text: string (required); variant?: one of h1|h2|h3|h4|h5|caption|body.',\n },\n {\n name: 'Image',\n description: 'Displays an image from a URL.',\n props:\n 'url: string (required); description?: string; fit?: contain|cover|fill|none|scaleDown; variant?: icon|avatar|smallFeature|mediumFeature|largeFeature|header.',\n },\n {\n name: 'Icon',\n description:\n 'Displays a named material icon. `name` MUST be one of the exact names ' +\n 'listed below — do NOT invent names (e.g. there is no \"cloud\", \"air\", ' +\n 'or \"thermostat\"). If none fits, omit the Icon rather than guessing.',\n props: `name: one of ${BASIC_ICON_NAMES.join(', ')} (required, exact).`,\n },\n {\n name: 'Row',\n description: 'Lays out children horizontally.',\n props:\n 'children: string[] of component ids (required); justify?: start|center|end|spaceAround|spaceBetween|spaceEvenly|stretch; align?: start|center|end|stretch.',\n },\n {\n name: 'Column',\n description: 'Lays out children vertically.',\n props:\n 'children: string[] of component ids (required); justify?: start|center|end|spaceBetween|spaceAround|spaceEvenly|stretch; align?: start|center|end|stretch.',\n },\n {\n name: 'List',\n description: 'A list of children.',\n props:\n 'children: string[] of component ids (required); direction?: vertical|horizontal; listStyle?: ordered|unordered|none.',\n },\n {\n name: 'Card',\n description: 'A visually-contained card wrapping a single child.',\n props:\n 'child: string id of a single child component (required; wrap multiple in a Column/Row).',\n },\n {\n name: 'Divider',\n description: 'A horizontal or vertical separator line.',\n props: 'axis?: horizontal|vertical.',\n },\n {\n name: 'Button',\n description: 'A clickable button that fires an action back to the agent.',\n props:\n 'child: string id of a child (usually a Text) (required); variant?: default|primary|borderless; action: { event: { name: string, context?: object } } (required — the event name is sent back to the agent when clicked).',\n },\n {\n name: 'TextField',\n description: 'A single- or multi-line text input.',\n props:\n 'label: string (required); value?: string or { path } binding; variant?: shortText|longText|number|obscured.',\n },\n {\n name: 'CheckBox',\n description: 'A labeled checkbox.',\n props:\n 'label: string (required); value: boolean or { path } binding (required).',\n },\n {\n name: 'Slider',\n description: 'A numeric slider.',\n props:\n 'max: number (required); value: number or { path } binding (required); label?: string; min?: number; step?: number.',\n },\n ],\n};\n\n/**\n * Builds the \"make it look good\" styling tips, scoped to the components the\n * catalog actually provides so a custom catalog is never told to emit\n * components it lacks (which would then fail `validate: 'strict'`).\n */\nfunction renderStyleTips(has: Set<string>): string {\n const tips: string[] = [];\n const containers = ['Card', 'Column', 'Row'].filter((c) => has.has(c));\n if (containers.length) {\n tips.push(\n `- Group related content with layout components (${containers.join(\n '/'\n )}) and give it a clear hierarchy.`\n );\n }\n if (has.has('Text')) {\n tips.push(\n '- Give titles a heading `variant` (e.g. h2/h3) and secondary text the ' +\n '`caption` variant instead of embedding \"#\"/\"##\" heading markers in ' +\n 'the text.'\n );\n }\n const accents = ['Icon', 'Divider', 'Image'].filter((c) => has.has(c));\n if (accents.length) {\n tips.push(\n `- Use ${accents.join(\n '/'\n )} to add visual meaning and separate sections where it helps.`\n );\n }\n if (has.has('Button')) {\n tips.push('- Give primary buttons `variant: \"primary\"`.');\n }\n return tips.length\n ? `\\n\\nMake it look good, not bland:\\n${tips.join('\\n')}`\n : '';\n}\n\n/**\n * Builds a worked example. Uses a rich Card/Column/Text layout when the catalog\n * supports it (the common case, e.g. the basic catalog); otherwise falls back\n * to a minimal example built only from components the catalog provides, so the\n * example never references unknown components.\n */\nfunction renderExample(catalog: A2uiCatalog, has: Set<string>): string {\n if (has.has('Card') && has.has('Column') && has.has('Text')) {\n return `\n\nExample (a small weather card):\n\\`\\`\\`a2ui\n[\n { \"createSurface\": { \"surfaceId\": \"SURFACE_ID\", \"catalogId\": \"${catalog.id}\" } },\n { \"updateComponents\": { \"surfaceId\": \"SURFACE_ID\", \"components\": [\n { \"id\": \"root\", \"component\": \"Card\", \"child\": \"body\" },\n { \"id\": \"body\", \"component\": \"Column\", \"children\": [\"title\", \"temp\"] },\n { \"id\": \"title\", \"component\": \"Text\", \"text\": \"Weather in Tokyo\", \"variant\": \"h3\" },\n { \"id\": \"temp\", \"component\": \"Text\", \"text\": { \"path\": \"/temp\" } }\n ] } },\n { \"updateDataModel\": { \"surfaceId\": \"SURFACE_ID\", \"path\": \"/temp\", \"value\": \"18°C\" } }\n]\n\\`\\`\\``;\n }\n // Minimal fallback: root uses whatever the catalog's first component is.\n const rootComponent = catalog.components[0]?.name ?? 'Text';\n return `\n\nExample (a minimal surface):\n\\`\\`\\`a2ui\n[\n { \"createSurface\": { \"surfaceId\": \"SURFACE_ID\", \"catalogId\": \"${catalog.id}\" } },\n { \"updateComponents\": { \"surfaceId\": \"SURFACE_ID\", \"components\": [\n { \"id\": \"root\", \"component\": \"${rootComponent}\" }\n ] } }\n]\n\\`\\`\\``;\n}\n\n/**\n * Renders a catalog into model-facing instructions describing the A2UI protocol\n * and the available components. Injected into the system prompt by the\n * middleware when `instructions !== 'none'`.\n */\nexport function renderCatalogInstructions(catalog: A2uiCatalog): string {\n const componentDocs = catalog.components\n .map((c) => `- ${c.name}: ${c.description} Props: ${c.props}`)\n .join('\\n');\n\n const has = new Set(catalog.components.map((c) => c.name));\n const styleSection = renderStyleTips(has);\n const exampleSection = renderExample(catalog, has);\n\n // Forms guidance only applies if the catalog has input components.\n const inputs = ['TextField', 'CheckBox', 'Slider'].filter((c) => has.has(c));\n const inputList = inputs.join(', ');\n const formsSection = inputs.length\n ? `\n- Forms: input components (${inputList}) do NOT send their values automatically.\n To capture what the user entered you MUST do BOTH of these:\n 1. Bind each input's \\`value\\` to a data-model path, e.g.\n \\`{ \"component\": \"TextField\", \"label\": \"Email\", \"value\": { \"path\": \"/email\" } }\\`.\n Typing updates the data model at that path.\n 2. On the submit \\`Button\\`, echo those same paths in\n \\`action.event.context\\` so their current values are sent back to you, e.g.\n \\`\"context\": { \"email\": { \"path\": \"/email\" }, \"name\": { \"path\": \"/name\" } }\\`.\n Without the \\`{ path }\\` bindings and the button \\`context\\`, the action arrives\n with an empty \\`context\\` and the entered values are lost.`\n : '';\n\n return `# Rendering UI with A2UI\n\nYou can render rich, interactive UI (not just text) by emitting an A2UI surface.\nWhen a result is better *shown* than *told* (weather, lists, forms, comparisons,\nconfirmations, anything visual or interactive), render a UI surface.\n\nTo render UI, output a single fenced code block tagged \\`a2ui\\` containing a JSON\narray of A2UI envelope messages. You may still write normal prose before it.\n\nRules:\n- The UI is an ADJACENCY LIST: a flat array of components. Build the tree using\n string \\`id\\` references, NOT nested objects. Exactly one component MUST have\n \\`id: \"root\"\\`.\n- Every component has a \\`component\\` (type name) and an \\`id\\`. Container\n components reference their children by id via a \\`children\\` array; single-child\n wrappers reference one \\`child\\` id.\n- Values can be literals, or a data-model binding \\`{ \"path\": \"/somePath\" }\\`.\n- Use \\`createSurface\\` first (with \\`catalogId\\`), then \\`updateComponents\\` to add\n the component list, then optionally \\`updateDataModel\\` to set data. You may\n combine them in one array, in order.\n- Interactive components fire an \\`action\\` with an event \\`name\\`; that name is\n sent back to you when the user interacts, so choose meaningful names.${formsSection}\n- When a user interacts with a surface (e.g. presses a button) and you respond\n with updated UI, RE-RENDER THE WHOLE SURFACE: start again with\n \\`createSurface\\` followed by \\`updateComponents\\`. Do not emit a bare\n \\`updateDataModel\\`/\\`updateComponents\\` expecting a previous surface to still\n exist.${styleSection}\n\nThe catalogId to use is:\n\"${catalog.id}\"\n\nAvailable components:\n${componentDocs}${exampleSection}\n\nDo not explain the JSON; just render the block. Use \"SURFACE_ID\" literally as a\nplaceholder for the surface id — the system replaces it with a real id.`;\n}\n\n/** The literal placeholder the model is told to use for surface ids. */\nexport const SURFACE_ID_PLACEHOLDER = 'SURFACE_ID';\n"],"mappings":"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","names":[]}