@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
@@ -0,0 +1 @@
1
+ {"version":3,"sources":["../src/loader.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 * Catalog loading helpers.\n *\n * The {@link a2ui} middleware references catalogs by id and looks them up in the\n * Genkit registry (value type {@link A2UI_CATALOG_VALUE_TYPE}). {@link loadCatalog}\n * registers a catalog under an id so the middleware — and, in the future, Genkit\n * tooling — can find it. Provide the catalog inline, or load it from a JSON file.\n *\n * This module uses Node's `fs` for the `file` variant, so it is server-only (it\n * is exported from the main `@genkit-ai/a2ui` entry, not `@genkit-ai/a2ui/client`).\n *\n * @module\n */\n\nimport type { HasRegistry } from 'genkit/registry';\n\nimport {\n A2UI_CATALOG_VALUE_TYPE,\n DEFAULT_CATALOG_ID,\n basicCatalog,\n type A2uiCatalog,\n} from './catalog.js';\n\n/** Options for {@link loadCatalog}. Provide either `catalog` or `file`. */\nexport type LoadCatalogOptions = {\n /** The id to register the catalog under (referenced by `a2ui({ catalog })`). */\n id: string;\n} & (\n | {\n /** An in-memory catalog to register. */\n catalog: A2uiCatalog;\n file?: never;\n }\n | {\n /** Path to a JSON file containing a catalog. */\n file: string;\n catalog?: never;\n }\n);\n\n/**\n * Registers an A2UI catalog under an id so the {@link a2ui} middleware can look\n * it up (via `a2ui({ catalog: id })`).\n *\n * @example\n * ```ts\n * // From an in-memory object:\n * await loadCatalog(ai, { id: 'my-catalog', catalog: myCatalog });\n *\n * // From a JSON file:\n * await loadCatalog(ai, { id: 'my-catalog', file: './my-catalog.json' });\n * ```\n *\n * @returns The registered catalog (with its `id` set to the given id).\n */\nexport async function loadCatalog(\n ai: HasRegistry,\n options: LoadCatalogOptions\n): Promise<A2uiCatalog> {\n const { id } = options;\n if (!id) {\n throw new Error('loadCatalog(): `id` is required.');\n }\n\n let catalog: A2uiCatalog;\n if (options.file !== undefined) {\n catalog = await readCatalogFile(options.file);\n } else if (options.catalog !== undefined) {\n catalog = options.catalog;\n } else {\n throw new Error('loadCatalog(): provide either `catalog` or `file`.');\n }\n\n if (!catalog || !Array.isArray(catalog.components)) {\n throw new Error(\n `loadCatalog(): catalog \"${id}\" must have a \"components\" array.`\n );\n }\n\n // Register under the requested id. Keep the catalog's own `id` (used as the\n // `catalogId` on surfaces) intact if present; otherwise default it to the id.\n // Use `||` (not `??`) so an empty-string id in the source catalog also falls\n // back — an empty `catalogId` would otherwise propagate onto every surface.\n const registered: A2uiCatalog = { ...catalog, id: catalog.id || id };\n ai.registry.registerValue(A2UI_CATALOG_VALUE_TYPE, id, registered);\n return registered;\n}\n\n/** Reads and parses a catalog from a JSON file. */\nasync function readCatalogFile(file: string): Promise<A2uiCatalog> {\n // Import lazily so this module stays importable in non-Node contexts until\n // the `file` variant is actually used.\n const { readFile } = await import('node:fs/promises');\n let raw: string;\n try {\n raw = await readFile(file, 'utf8');\n } catch (e) {\n throw new Error(\n `loadCatalog(): failed to read catalog file \"${file}\": ${(e as Error).message}`\n );\n }\n try {\n return JSON.parse(raw) as A2uiCatalog;\n } catch (e) {\n throw new Error(\n `loadCatalog(): catalog file \"${file}\" is not valid JSON: ${(e as Error).message}`\n );\n }\n}\n\n/**\n * Resolves a catalog by id from the registry, falling back to the bundled\n * {@link basicCatalog} for the default id. Used by the {@link a2ui} middleware.\n *\n * @internal\n */\nexport async function resolveCatalog(\n ai: HasRegistry,\n id: string\n): Promise<A2uiCatalog> {\n const found = await ai.registry.lookupValue<A2uiCatalog>(\n A2UI_CATALOG_VALUE_TYPE,\n id\n );\n if (found) return found;\n if (id === DEFAULT_CATALOG_ID) return basicCatalog;\n throw new Error(\n `a2ui(): no catalog registered under id \"${id}\". ` +\n `Register one with loadCatalog(ai, { id: \"${id}\", catalog }) or use the ` +\n `default \"${DEFAULT_CATALOG_ID}\" catalog.`\n );\n}\n"],"mappings":";;;;;;;;;;;;;;;;;;;;;;;;;;;;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAgCA,qBAKO;AAkCP,eAAsB,YACpB,IACA,SACsB;AACtB,QAAM,EAAE,GAAG,IAAI;AACf,MAAI,CAAC,IAAI;AACP,UAAM,IAAI,MAAM,kCAAkC;AAAA,EACpD;AAEA,MAAI;AACJ,MAAI,QAAQ,SAAS,QAAW;AAC9B,cAAU,MAAM,gBAAgB,QAAQ,IAAI;AAAA,EAC9C,WAAW,QAAQ,YAAY,QAAW;AACxC,cAAU,QAAQ;AAAA,EACpB,OAAO;AACL,UAAM,IAAI,MAAM,oDAAoD;AAAA,EACtE;AAEA,MAAI,CAAC,WAAW,CAAC,MAAM,QAAQ,QAAQ,UAAU,GAAG;AAClD,UAAM,IAAI;AAAA,MACR,2BAA2B,EAAE;AAAA,IAC/B;AAAA,EACF;AAMA,QAAM,aAA0B,EAAE,GAAG,SAAS,IAAI,QAAQ,MAAM,GAAG;AACnE,KAAG,SAAS,cAAc,wCAAyB,IAAI,UAAU;AACjE,SAAO;AACT;AAGA,eAAe,gBAAgB,MAAoC;AAGjE,QAAM,EAAE,SAAS,IAAI,MAAM,OAAO,kBAAkB;AACpD,MAAI;AACJ,MAAI;AACF,UAAM,MAAM,SAAS,MAAM,MAAM;AAAA,EACnC,SAAS,GAAG;AACV,UAAM,IAAI;AAAA,MACR,+CAA+C,IAAI,MAAO,EAAY,OAAO;AAAA,IAC/E;AAAA,EACF;AACA,MAAI;AACF,WAAO,KAAK,MAAM,GAAG;AAAA,EACvB,SAAS,GAAG;AACV,UAAM,IAAI;AAAA,MACR,gCAAgC,IAAI,wBAAyB,EAAY,OAAO;AAAA,IAClF;AAAA,EACF;AACF;AAQA,eAAsB,eACpB,IACA,IACsB;AACtB,QAAM,QAAQ,MAAM,GAAG,SAAS;AAAA,IAC9B;AAAA,IACA;AAAA,EACF;AACA,MAAI,MAAO,QAAO;AAClB,MAAI,OAAO,kCAAoB,QAAO;AACtC,QAAM,IAAI;AAAA,IACR,2CAA2C,EAAE,+CACC,EAAE,qCAClC,iCAAkB;AAAA,EAClC;AACF;","names":[]}
package/lib/loader.mjs ADDED
@@ -0,0 +1,61 @@
1
+ import {
2
+ A2UI_CATALOG_VALUE_TYPE,
3
+ DEFAULT_CATALOG_ID,
4
+ basicCatalog
5
+ } from "./catalog.mjs";
6
+ async function loadCatalog(ai, options) {
7
+ const { id } = options;
8
+ if (!id) {
9
+ throw new Error("loadCatalog(): `id` is required.");
10
+ }
11
+ let catalog;
12
+ if (options.file !== void 0) {
13
+ catalog = await readCatalogFile(options.file);
14
+ } else if (options.catalog !== void 0) {
15
+ catalog = options.catalog;
16
+ } else {
17
+ throw new Error("loadCatalog(): provide either `catalog` or `file`.");
18
+ }
19
+ if (!catalog || !Array.isArray(catalog.components)) {
20
+ throw new Error(
21
+ `loadCatalog(): catalog "${id}" must have a "components" array.`
22
+ );
23
+ }
24
+ const registered = { ...catalog, id: catalog.id || id };
25
+ ai.registry.registerValue(A2UI_CATALOG_VALUE_TYPE, id, registered);
26
+ return registered;
27
+ }
28
+ async function readCatalogFile(file) {
29
+ const { readFile } = await import("node:fs/promises");
30
+ let raw;
31
+ try {
32
+ raw = await readFile(file, "utf8");
33
+ } catch (e) {
34
+ throw new Error(
35
+ `loadCatalog(): failed to read catalog file "${file}": ${e.message}`
36
+ );
37
+ }
38
+ try {
39
+ return JSON.parse(raw);
40
+ } catch (e) {
41
+ throw new Error(
42
+ `loadCatalog(): catalog file "${file}" is not valid JSON: ${e.message}`
43
+ );
44
+ }
45
+ }
46
+ async function resolveCatalog(ai, id) {
47
+ const found = await ai.registry.lookupValue(
48
+ A2UI_CATALOG_VALUE_TYPE,
49
+ id
50
+ );
51
+ if (found) return found;
52
+ if (id === DEFAULT_CATALOG_ID) return basicCatalog;
53
+ throw new Error(
54
+ `a2ui(): no catalog registered under id "${id}". Register one with loadCatalog(ai, { id: "${id}", catalog }) or use the default "${DEFAULT_CATALOG_ID}" catalog.`
55
+ );
56
+ }
57
+ export {
58
+ loadCatalog,
59
+ resolveCatalog
60
+ };
61
+ //# sourceMappingURL=loader.mjs.map
@@ -0,0 +1 @@
1
+ {"version":3,"sources":["../src/loader.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 * Catalog loading helpers.\n *\n * The {@link a2ui} middleware references catalogs by id and looks them up in the\n * Genkit registry (value type {@link A2UI_CATALOG_VALUE_TYPE}). {@link loadCatalog}\n * registers a catalog under an id so the middleware — and, in the future, Genkit\n * tooling — can find it. Provide the catalog inline, or load it from a JSON file.\n *\n * This module uses Node's `fs` for the `file` variant, so it is server-only (it\n * is exported from the main `@genkit-ai/a2ui` entry, not `@genkit-ai/a2ui/client`).\n *\n * @module\n */\n\nimport type { HasRegistry } from 'genkit/registry';\n\nimport {\n A2UI_CATALOG_VALUE_TYPE,\n DEFAULT_CATALOG_ID,\n basicCatalog,\n type A2uiCatalog,\n} from './catalog.mjs';\n\n/** Options for {@link loadCatalog}. Provide either `catalog` or `file`. */\nexport type LoadCatalogOptions = {\n /** The id to register the catalog under (referenced by `a2ui({ catalog })`). */\n id: string;\n} & (\n | {\n /** An in-memory catalog to register. */\n catalog: A2uiCatalog;\n file?: never;\n }\n | {\n /** Path to a JSON file containing a catalog. */\n file: string;\n catalog?: never;\n }\n);\n\n/**\n * Registers an A2UI catalog under an id so the {@link a2ui} middleware can look\n * it up (via `a2ui({ catalog: id })`).\n *\n * @example\n * ```ts\n * // From an in-memory object:\n * await loadCatalog(ai, { id: 'my-catalog', catalog: myCatalog });\n *\n * // From a JSON file:\n * await loadCatalog(ai, { id: 'my-catalog', file: './my-catalog.json' });\n * ```\n *\n * @returns The registered catalog (with its `id` set to the given id).\n */\nexport async function loadCatalog(\n ai: HasRegistry,\n options: LoadCatalogOptions\n): Promise<A2uiCatalog> {\n const { id } = options;\n if (!id) {\n throw new Error('loadCatalog(): `id` is required.');\n }\n\n let catalog: A2uiCatalog;\n if (options.file !== undefined) {\n catalog = await readCatalogFile(options.file);\n } else if (options.catalog !== undefined) {\n catalog = options.catalog;\n } else {\n throw new Error('loadCatalog(): provide either `catalog` or `file`.');\n }\n\n if (!catalog || !Array.isArray(catalog.components)) {\n throw new Error(\n `loadCatalog(): catalog \"${id}\" must have a \"components\" array.`\n );\n }\n\n // Register under the requested id. Keep the catalog's own `id` (used as the\n // `catalogId` on surfaces) intact if present; otherwise default it to the id.\n // Use `||` (not `??`) so an empty-string id in the source catalog also falls\n // back — an empty `catalogId` would otherwise propagate onto every surface.\n const registered: A2uiCatalog = { ...catalog, id: catalog.id || id };\n ai.registry.registerValue(A2UI_CATALOG_VALUE_TYPE, id, registered);\n return registered;\n}\n\n/** Reads and parses a catalog from a JSON file. */\nasync function readCatalogFile(file: string): Promise<A2uiCatalog> {\n // Import lazily so this module stays importable in non-Node contexts until\n // the `file` variant is actually used.\n const { readFile } = await import('node:fs/promises');\n let raw: string;\n try {\n raw = await readFile(file, 'utf8');\n } catch (e) {\n throw new Error(\n `loadCatalog(): failed to read catalog file \"${file}\": ${(e as Error).message}`\n );\n }\n try {\n return JSON.parse(raw) as A2uiCatalog;\n } catch (e) {\n throw new Error(\n `loadCatalog(): catalog file \"${file}\" is not valid JSON: ${(e as Error).message}`\n );\n }\n}\n\n/**\n * Resolves a catalog by id from the registry, falling back to the bundled\n * {@link basicCatalog} for the default id. Used by the {@link a2ui} middleware.\n *\n * @internal\n */\nexport async function resolveCatalog(\n ai: HasRegistry,\n id: string\n): Promise<A2uiCatalog> {\n const found = await ai.registry.lookupValue<A2uiCatalog>(\n A2UI_CATALOG_VALUE_TYPE,\n id\n );\n if (found) return found;\n if (id === DEFAULT_CATALOG_ID) return basicCatalog;\n throw new Error(\n `a2ui(): no catalog registered under id \"${id}\". ` +\n `Register one with loadCatalog(ai, { id: \"${id}\", catalog }) or use the ` +\n `default \"${DEFAULT_CATALOG_ID}\" catalog.`\n );\n}\n"],"mappings":"AAgCA;AAAA,EACE;AAAA,EACA;AAAA,EACA;AAAA,OAEK;AAkCP,eAAsB,YACpB,IACA,SACsB;AACtB,QAAM,EAAE,GAAG,IAAI;AACf,MAAI,CAAC,IAAI;AACP,UAAM,IAAI,MAAM,kCAAkC;AAAA,EACpD;AAEA,MAAI;AACJ,MAAI,QAAQ,SAAS,QAAW;AAC9B,cAAU,MAAM,gBAAgB,QAAQ,IAAI;AAAA,EAC9C,WAAW,QAAQ,YAAY,QAAW;AACxC,cAAU,QAAQ;AAAA,EACpB,OAAO;AACL,UAAM,IAAI,MAAM,oDAAoD;AAAA,EACtE;AAEA,MAAI,CAAC,WAAW,CAAC,MAAM,QAAQ,QAAQ,UAAU,GAAG;AAClD,UAAM,IAAI;AAAA,MACR,2BAA2B,EAAE;AAAA,IAC/B;AAAA,EACF;AAMA,QAAM,aAA0B,EAAE,GAAG,SAAS,IAAI,QAAQ,MAAM,GAAG;AACnE,KAAG,SAAS,cAAc,yBAAyB,IAAI,UAAU;AACjE,SAAO;AACT;AAGA,eAAe,gBAAgB,MAAoC;AAGjE,QAAM,EAAE,SAAS,IAAI,MAAM,OAAO,kBAAkB;AACpD,MAAI;AACJ,MAAI;AACF,UAAM,MAAM,SAAS,MAAM,MAAM;AAAA,EACnC,SAAS,GAAG;AACV,UAAM,IAAI;AAAA,MACR,+CAA+C,IAAI,MAAO,EAAY,OAAO;AAAA,IAC/E;AAAA,EACF;AACA,MAAI;AACF,WAAO,KAAK,MAAM,GAAG;AAAA,EACvB,SAAS,GAAG;AACV,UAAM,IAAI;AAAA,MACR,gCAAgC,IAAI,wBAAyB,EAAY,OAAO;AAAA,IAClF;AAAA,EACF;AACF;AAQA,eAAsB,eACpB,IACA,IACsB;AACtB,QAAM,QAAQ,MAAM,GAAG,SAAS;AAAA,IAC9B;AAAA,IACA;AAAA,EACF;AACA,MAAI,MAAO,QAAO;AAClB,MAAI,OAAO,mBAAoB,QAAO;AACtC,QAAM,IAAI;AAAA,IACR,2CAA2C,EAAE,+CACC,EAAE,qCAClC,kBAAkB;AAAA,EAClC;AACF;","names":[]}
@@ -0,0 +1,112 @@
1
+ import { z, GenerateMiddleware } from 'genkit';
2
+
3
+ /**
4
+ * Copyright 2026 Google LLC
5
+ *
6
+ * Licensed under the Apache License, Version 2.0 (the "License");
7
+ * you may not use this file except in compliance with the License.
8
+ * You may obtain a copy of the License at
9
+ *
10
+ * http://www.apache.org/licenses/LICENSE-2.0
11
+ *
12
+ * Unless required by applicable law or agreed to in writing, software
13
+ * distributed under the License is distributed on an "AS IS" BASIS,
14
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
15
+ * See the License for the specific language governing permissions and
16
+ * limitations under the License.
17
+ */
18
+ /**
19
+ * The `a2ui()` generate middleware — the whole server-side integration.
20
+ *
21
+ * Add it to `defineAgent({ use: [...] })` or `ai.generate({ use: [...] })` and
22
+ * the agent gains the ability to render A2UI surfaces. The middleware:
23
+ *
24
+ * 1. Injects the catalog's capabilities into the system prompt so the model
25
+ * knows what UI it may render.
26
+ * 2. Intercepts the model's output (both the streamed chunks and the final
27
+ * message), extracts any `a2ui` fenced blocks, validates them against the
28
+ * catalog, and rewrites them into the canonical a2ui data part.
29
+ *
30
+ * Downstream (client transport, `@a2ui/web_core`) only ever sees a2ui parts —
31
+ * "pure vs mixed" turns are a prompting choice, not a separate code path.
32
+ *
33
+ * Implemented with `generateMiddleware` and the `model` hook, so it wraps each
34
+ * raw model call in the agent's tool loop.
35
+ *
36
+ * @module
37
+ */
38
+
39
+ /** Zod schema for the {@link a2ui} middleware configuration. */
40
+ declare const A2uiOptionsSchema: z.ZodObject<{
41
+ /**
42
+ * The id of the catalog describing what the agent may render. Defaults to
43
+ * `'basic'` (the bundled basic catalog). Register additional catalogs with
44
+ * `loadCatalog(ai, { id, catalog | file })` and reference them by id here.
45
+ */
46
+ catalog: z.ZodOptional<z.ZodString>;
47
+ /**
48
+ * Where to inject the catalog's capabilities. `'system'` (default) appends
49
+ * A2UI instructions to the system prompt; `'none'` injects nothing (useful if
50
+ * you supply your own instructions).
51
+ */
52
+ instructions: z.ZodOptional<z.ZodEnum<["system", "none"]>>;
53
+ /**
54
+ * Validate emitted envelopes against the catalog. `'warn'` (default) logs a
55
+ * warning and drops the offending block/envelope, keeping the rest of the
56
+ * turn alive; `'strict'` throws on malformed JSON or unknown components (best
57
+ * for development); `'off'` passes them through unchecked.
58
+ *
59
+ * IMPORTANT: this validates envelope structure and component *type names*
60
+ * against the catalog only. It does NOT validate component props or
61
+ * data-model values. Model-controlled values such as `Image.url` and `Text`
62
+ * (inline Markdown, which the renderer may turn into HTML) pass through
63
+ * untouched even under `'strict'`. `'strict'` is a well-formedness check, not
64
+ * a security boundary — see the "Security / trust boundary" section of the
65
+ * README. Prop sanitization is the renderer/catalog's responsibility, and
66
+ * hosts should CSP-restrict image and other remote sources.
67
+ */
68
+ validate: z.ZodOptional<z.ZodEnum<["strict", "warn", "off"]>>;
69
+ /**
70
+ * Surface id policy. Provide a fixed id to reuse for every surface. Defaults
71
+ * to a fresh UUID per surface.
72
+ */
73
+ surfaceId: z.ZodOptional<z.ZodString>;
74
+ /**
75
+ * Protocol version stamped on emitted envelopes. Defaults to `'v0.9'`.
76
+ * Constrained to the versions the renderer understands so a typo can't emit
77
+ * envelopes that fail at runtime.
78
+ */
79
+ version: z.ZodOptional<z.ZodEnum<[string, ...string[]]>>;
80
+ }, "strip", z.ZodTypeAny, {
81
+ catalog?: string | undefined;
82
+ instructions?: "system" | "none" | undefined;
83
+ validate?: "strict" | "warn" | "off" | undefined;
84
+ surfaceId?: string | undefined;
85
+ version?: string | undefined;
86
+ }, {
87
+ catalog?: string | undefined;
88
+ instructions?: "system" | "none" | undefined;
89
+ validate?: "strict" | "warn" | "off" | undefined;
90
+ surfaceId?: string | undefined;
91
+ version?: string | undefined;
92
+ }>;
93
+ /** Configuration for the {@link a2ui} middleware. */
94
+ type A2uiOptions = z.infer<typeof A2uiOptionsSchema>;
95
+ /**
96
+ * The A2UI generate middleware.
97
+ *
98
+ * @example
99
+ * ```ts
100
+ * import { a2ui } from '@genkit-ai/a2ui';
101
+ *
102
+ * export const uiAgent = ai.defineAgent({
103
+ * name: 'uiAgent',
104
+ * model: 'googleai/gemini-flash-latest',
105
+ * system: 'You help users. Render UI when it is clearer than prose.',
106
+ * use: [a2ui()], // defaults to the bundled 'basic' catalog
107
+ * });
108
+ * ```
109
+ */
110
+ declare const a2ui: GenerateMiddleware<typeof A2uiOptionsSchema>;
111
+
112
+ export { type A2uiOptions, A2uiOptionsSchema, a2ui };
@@ -0,0 +1,112 @@
1
+ import { z, GenerateMiddleware } from 'genkit';
2
+
3
+ /**
4
+ * Copyright 2026 Google LLC
5
+ *
6
+ * Licensed under the Apache License, Version 2.0 (the "License");
7
+ * you may not use this file except in compliance with the License.
8
+ * You may obtain a copy of the License at
9
+ *
10
+ * http://www.apache.org/licenses/LICENSE-2.0
11
+ *
12
+ * Unless required by applicable law or agreed to in writing, software
13
+ * distributed under the License is distributed on an "AS IS" BASIS,
14
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
15
+ * See the License for the specific language governing permissions and
16
+ * limitations under the License.
17
+ */
18
+ /**
19
+ * The `a2ui()` generate middleware — the whole server-side integration.
20
+ *
21
+ * Add it to `defineAgent({ use: [...] })` or `ai.generate({ use: [...] })` and
22
+ * the agent gains the ability to render A2UI surfaces. The middleware:
23
+ *
24
+ * 1. Injects the catalog's capabilities into the system prompt so the model
25
+ * knows what UI it may render.
26
+ * 2. Intercepts the model's output (both the streamed chunks and the final
27
+ * message), extracts any `a2ui` fenced blocks, validates them against the
28
+ * catalog, and rewrites them into the canonical a2ui data part.
29
+ *
30
+ * Downstream (client transport, `@a2ui/web_core`) only ever sees a2ui parts —
31
+ * "pure vs mixed" turns are a prompting choice, not a separate code path.
32
+ *
33
+ * Implemented with `generateMiddleware` and the `model` hook, so it wraps each
34
+ * raw model call in the agent's tool loop.
35
+ *
36
+ * @module
37
+ */
38
+
39
+ /** Zod schema for the {@link a2ui} middleware configuration. */
40
+ declare const A2uiOptionsSchema: z.ZodObject<{
41
+ /**
42
+ * The id of the catalog describing what the agent may render. Defaults to
43
+ * `'basic'` (the bundled basic catalog). Register additional catalogs with
44
+ * `loadCatalog(ai, { id, catalog | file })` and reference them by id here.
45
+ */
46
+ catalog: z.ZodOptional<z.ZodString>;
47
+ /**
48
+ * Where to inject the catalog's capabilities. `'system'` (default) appends
49
+ * A2UI instructions to the system prompt; `'none'` injects nothing (useful if
50
+ * you supply your own instructions).
51
+ */
52
+ instructions: z.ZodOptional<z.ZodEnum<["system", "none"]>>;
53
+ /**
54
+ * Validate emitted envelopes against the catalog. `'warn'` (default) logs a
55
+ * warning and drops the offending block/envelope, keeping the rest of the
56
+ * turn alive; `'strict'` throws on malformed JSON or unknown components (best
57
+ * for development); `'off'` passes them through unchecked.
58
+ *
59
+ * IMPORTANT: this validates envelope structure and component *type names*
60
+ * against the catalog only. It does NOT validate component props or
61
+ * data-model values. Model-controlled values such as `Image.url` and `Text`
62
+ * (inline Markdown, which the renderer may turn into HTML) pass through
63
+ * untouched even under `'strict'`. `'strict'` is a well-formedness check, not
64
+ * a security boundary — see the "Security / trust boundary" section of the
65
+ * README. Prop sanitization is the renderer/catalog's responsibility, and
66
+ * hosts should CSP-restrict image and other remote sources.
67
+ */
68
+ validate: z.ZodOptional<z.ZodEnum<["strict", "warn", "off"]>>;
69
+ /**
70
+ * Surface id policy. Provide a fixed id to reuse for every surface. Defaults
71
+ * to a fresh UUID per surface.
72
+ */
73
+ surfaceId: z.ZodOptional<z.ZodString>;
74
+ /**
75
+ * Protocol version stamped on emitted envelopes. Defaults to `'v0.9'`.
76
+ * Constrained to the versions the renderer understands so a typo can't emit
77
+ * envelopes that fail at runtime.
78
+ */
79
+ version: z.ZodOptional<z.ZodEnum<[string, ...string[]]>>;
80
+ }, "strip", z.ZodTypeAny, {
81
+ catalog?: string | undefined;
82
+ instructions?: "system" | "none" | undefined;
83
+ validate?: "strict" | "warn" | "off" | undefined;
84
+ surfaceId?: string | undefined;
85
+ version?: string | undefined;
86
+ }, {
87
+ catalog?: string | undefined;
88
+ instructions?: "system" | "none" | undefined;
89
+ validate?: "strict" | "warn" | "off" | undefined;
90
+ surfaceId?: string | undefined;
91
+ version?: string | undefined;
92
+ }>;
93
+ /** Configuration for the {@link a2ui} middleware. */
94
+ type A2uiOptions = z.infer<typeof A2uiOptionsSchema>;
95
+ /**
96
+ * The A2UI generate middleware.
97
+ *
98
+ * @example
99
+ * ```ts
100
+ * import { a2ui } from '@genkit-ai/a2ui';
101
+ *
102
+ * export const uiAgent = ai.defineAgent({
103
+ * name: 'uiAgent',
104
+ * model: 'googleai/gemini-flash-latest',
105
+ * system: 'You help users. Render UI when it is clearer than prose.',
106
+ * use: [a2ui()], // defaults to the bundled 'basic' catalog
107
+ * });
108
+ * ```
109
+ */
110
+ declare const a2ui: GenerateMiddleware<typeof A2uiOptionsSchema>;
111
+
112
+ export { type A2uiOptions, A2uiOptionsSchema, a2ui };
@@ -0,0 +1,259 @@
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 middleware_exports = {};
20
+ __export(middleware_exports, {
21
+ A2uiOptionsSchema: () => A2uiOptionsSchema,
22
+ a2ui: () => a2ui
23
+ });
24
+ module.exports = __toCommonJS(middleware_exports);
25
+ var import_genkit = require("genkit");
26
+ var import_catalog = require("./catalog.js");
27
+ var import_loader = require("./loader.js");
28
+ var import_parser = require("./parser.js");
29
+ var import_part = require("./part.js");
30
+ var import_types = require("./types.js");
31
+ const A2uiOptionsSchema = import_genkit.z.object({
32
+ /**
33
+ * The id of the catalog describing what the agent may render. Defaults to
34
+ * `'basic'` (the bundled basic catalog). Register additional catalogs with
35
+ * `loadCatalog(ai, { id, catalog | file })` and reference them by id here.
36
+ */
37
+ catalog: import_genkit.z.string().optional(),
38
+ /**
39
+ * Where to inject the catalog's capabilities. `'system'` (default) appends
40
+ * A2UI instructions to the system prompt; `'none'` injects nothing (useful if
41
+ * you supply your own instructions).
42
+ */
43
+ instructions: import_genkit.z.enum(["system", "none"]).optional(),
44
+ /**
45
+ * Validate emitted envelopes against the catalog. `'warn'` (default) logs a
46
+ * warning and drops the offending block/envelope, keeping the rest of the
47
+ * turn alive; `'strict'` throws on malformed JSON or unknown components (best
48
+ * for development); `'off'` passes them through unchecked.
49
+ *
50
+ * IMPORTANT: this validates envelope structure and component *type names*
51
+ * against the catalog only. It does NOT validate component props or
52
+ * data-model values. Model-controlled values such as `Image.url` and `Text`
53
+ * (inline Markdown, which the renderer may turn into HTML) pass through
54
+ * untouched even under `'strict'`. `'strict'` is a well-formedness check, not
55
+ * a security boundary — see the "Security / trust boundary" section of the
56
+ * README. Prop sanitization is the renderer/catalog's responsibility, and
57
+ * hosts should CSP-restrict image and other remote sources.
58
+ */
59
+ validate: import_genkit.z.enum(["strict", "warn", "off"]).optional(),
60
+ /**
61
+ * Surface id policy. Provide a fixed id to reuse for every surface. Defaults
62
+ * to a fresh UUID per surface.
63
+ */
64
+ surfaceId: import_genkit.z.string().optional(),
65
+ /**
66
+ * Protocol version stamped on emitted envelopes. Defaults to `'v0.9'`.
67
+ * Constrained to the versions the renderer understands so a typo can't emit
68
+ * envelopes that fail at runtime.
69
+ */
70
+ version: import_genkit.z.enum(import_types.SUPPORTED_VERSIONS).optional()
71
+ });
72
+ function isTextPart(part) {
73
+ return !!part && typeof part === "object" && typeof part.text === "string";
74
+ }
75
+ function surfaceIdFactory(policy) {
76
+ if (typeof policy === "string") return () => policy;
77
+ return () => globalThis.crypto.randomUUID();
78
+ }
79
+ function replayableSurfaceIds(base) {
80
+ const generated = [];
81
+ let cursor = 0;
82
+ const next = () => {
83
+ const id = base();
84
+ generated.push(id);
85
+ return id;
86
+ };
87
+ const replayNext = () => cursor < generated.length ? generated[cursor++] : next();
88
+ const reset = () => {
89
+ cursor = 0;
90
+ };
91
+ return { next, replayNext, reset };
92
+ }
93
+ function partsFromParse(result) {
94
+ const out = [];
95
+ for (const seg of result.segments) {
96
+ if ("prose" in seg) {
97
+ if (seg.prose) out.push({ text: seg.prose });
98
+ } else {
99
+ out.push((0, import_part.a2uiPart)(seg.envelopes));
100
+ }
101
+ }
102
+ return out;
103
+ }
104
+ const a2ui = (0, import_genkit.generateMiddleware)(
105
+ {
106
+ name: "a2ui",
107
+ description: "Adds A2UI (Agent-to-UI) streaming UI support: injects catalog capabilities into the prompt and rewrites emitted UI blocks into a2ui data parts.",
108
+ configSchema: A2uiOptionsSchema
109
+ },
110
+ (options) => {
111
+ const { ai, config } = options;
112
+ const {
113
+ catalog: catalogId = import_catalog.DEFAULT_CATALOG_ID,
114
+ instructions = "system",
115
+ validate = "warn",
116
+ version = import_types.A2UI_VERSION
117
+ } = config ?? {};
118
+ const nextSurfaceId = surfaceIdFactory(config?.surfaceId);
119
+ return {
120
+ model: async (req, ctx, next) => {
121
+ const catalog = await (0, import_loader.resolveCatalog)(ai, catalogId);
122
+ const surfaceIds = replayableSurfaceIds(nextSurfaceId);
123
+ const sanitized = sanitizeInboundA2ui(req);
124
+ const request = instructions === "none" ? sanitized : injectInstructions(sanitized, catalog);
125
+ const streamParser = new import_parser.A2uiStreamParser({
126
+ catalog,
127
+ validate,
128
+ version,
129
+ surfaceId: surfaceIds.next
130
+ });
131
+ const originalOnChunk = ctx?.onChunk;
132
+ const wrappedCtx = originalOnChunk ? {
133
+ ...ctx,
134
+ onChunk: (chunk) => {
135
+ const transformed = transformChunk(chunk, streamParser);
136
+ if (transformed) originalOnChunk(transformed);
137
+ }
138
+ } : ctx;
139
+ const response = await next(request, wrappedCtx);
140
+ if (originalOnChunk) {
141
+ const tail = partsFromParse(streamParser.flush());
142
+ if (tail.length > 0) originalOnChunk({ content: tail });
143
+ }
144
+ surfaceIds.reset();
145
+ return transformResponse(response, {
146
+ catalog,
147
+ validate,
148
+ version,
149
+ surfaceId: surfaceIds.replayNext
150
+ });
151
+ }
152
+ };
153
+ }
154
+ );
155
+ function injectInstructions(req, catalog) {
156
+ const text = (0, import_catalog.renderCatalogInstructions)(catalog);
157
+ const messages = [...req.messages];
158
+ const sysIdx = messages.findIndex((m) => m.role === "system");
159
+ if (sysIdx >= 0) {
160
+ const sys = messages[sysIdx];
161
+ messages[sysIdx] = {
162
+ ...sys,
163
+ content: [...sys.content, { text: "\n\n" + text }]
164
+ };
165
+ } else {
166
+ messages.unshift({ role: "system", content: [{ text }] });
167
+ }
168
+ return { ...req, messages };
169
+ }
170
+ function transformChunk(chunk, parser) {
171
+ if (!chunk?.content || chunk.content.length === 0) return chunk;
172
+ const newContent = [];
173
+ for (const part of chunk.content) {
174
+ if (isTextPart(part) && part.text !== "") {
175
+ newContent.push(...partsFromParse(parser.push(part.text)));
176
+ } else {
177
+ newContent.push(part);
178
+ }
179
+ }
180
+ if (newContent.length === 0) return null;
181
+ return { ...chunk, content: newContent };
182
+ }
183
+ function transformResponse(response, opts) {
184
+ const message = response.message;
185
+ if (!message?.content) return response;
186
+ const parser = new import_parser.A2uiStreamParser(opts);
187
+ const newContent = [];
188
+ for (const part of message.content) {
189
+ if (isTextPart(part)) {
190
+ const pushed = parser.push(part.text);
191
+ const flushed = parser.flush();
192
+ const segments = [...pushed.segments, ...flushed.segments];
193
+ newContent.push(...partsFromParse({ segments }));
194
+ } else {
195
+ newContent.push(part);
196
+ }
197
+ }
198
+ return {
199
+ ...response,
200
+ message: { ...message, content: newContent }
201
+ };
202
+ }
203
+ function sanitizeInboundA2ui(req) {
204
+ let changed = false;
205
+ const messages = [];
206
+ for (const message of req.messages) {
207
+ if (!Array.isArray(message.content)) {
208
+ messages.push(message);
209
+ continue;
210
+ }
211
+ let msgChanged = false;
212
+ const content = [];
213
+ for (const part of message.content) {
214
+ if ((0, import_part.isA2uiPart)(part)) {
215
+ msgChanged = true;
216
+ const text = summarizeA2uiPart(part.data.envelopes);
217
+ if (text) content.push({ text });
218
+ } else {
219
+ content.push(part);
220
+ }
221
+ }
222
+ if (!msgChanged) {
223
+ messages.push(message);
224
+ continue;
225
+ }
226
+ changed = true;
227
+ if (content.length === 0) continue;
228
+ messages.push({ ...message, content });
229
+ }
230
+ return changed ? { ...req, messages } : req;
231
+ }
232
+ function summarizeA2uiPart(envelopes) {
233
+ const out = [];
234
+ let pendingSurface = [];
235
+ const flushSurface = () => {
236
+ if (pendingSurface.length === 0) return;
237
+ out.push("```a2ui\n" + JSON.stringify(pendingSurface) + "\n```");
238
+ pendingSurface = [];
239
+ };
240
+ for (const env of envelopes) {
241
+ if (!env || typeof env !== "object") continue;
242
+ if ("action" in env && env.action) {
243
+ flushSurface();
244
+ const a = env.action;
245
+ const ctx = a.context && Object.keys(a.context).length ? ` context=${JSON.stringify(a.context)}` : "";
246
+ out.push(`[UI action "${a.name}" on surface ${a.surfaceId}${ctx}]`);
247
+ } else if ("createSurface" in env && env.createSurface || "updateComponents" in env && env.updateComponents || "updateDataModel" in env && env.updateDataModel || "deleteSurface" in env && env.deleteSurface) {
248
+ pendingSurface.push(env);
249
+ }
250
+ }
251
+ flushSurface();
252
+ return out.join("\n");
253
+ }
254
+ // Annotate the CommonJS export names for ESM import in node:
255
+ 0 && (module.exports = {
256
+ A2uiOptionsSchema,
257
+ a2ui
258
+ });
259
+ //# sourceMappingURL=middleware.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"sources":["../src/middleware.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 * The `a2ui()` generate middleware — the whole server-side integration.\n *\n * Add it to `defineAgent({ use: [...] })` or `ai.generate({ use: [...] })` and\n * the agent gains the ability to render A2UI surfaces. The middleware:\n *\n * 1. Injects the catalog's capabilities into the system prompt so the model\n * knows what UI it may render.\n * 2. Intercepts the model's output (both the streamed chunks and the final\n * message), extracts any `a2ui` fenced blocks, validates them against the\n * catalog, and rewrites them into the canonical a2ui data part.\n *\n * Downstream (client transport, `@a2ui/web_core`) only ever sees a2ui parts —\n * \"pure vs mixed\" turns are a prompting choice, not a separate code path.\n *\n * Implemented with `generateMiddleware` and the `model` hook, so it wraps each\n * raw model call in the agent's tool loop.\n *\n * @module\n */\n\nimport { generateMiddleware, z, type GenerateMiddleware } from 'genkit';\nimport type {\n GenerateRequest,\n GenerateResponseChunkData,\n GenerateResponseData,\n MessageData,\n Part,\n TextPart,\n} from 'genkit/model';\nimport {\n DEFAULT_CATALOG_ID,\n renderCatalogInstructions,\n type A2uiCatalog,\n} from './catalog.js';\nimport { resolveCatalog } from './loader.js';\nimport { A2uiStreamParser, type ParseResult } from './parser.js';\nimport { a2uiPart, isA2uiPart } from './part.js';\nimport {\n A2UI_VERSION,\n SUPPORTED_VERSIONS,\n type A2uiEnvelope,\n} from './types.js';\n\n/** Zod schema for the {@link a2ui} middleware configuration. */\nexport const A2uiOptionsSchema = z.object({\n /**\n * The id of the catalog describing what the agent may render. Defaults to\n * `'basic'` (the bundled basic catalog). Register additional catalogs with\n * `loadCatalog(ai, { id, catalog | file })` and reference them by id here.\n */\n catalog: z.string().optional(),\n\n /**\n * Where to inject the catalog's capabilities. `'system'` (default) appends\n * A2UI instructions to the system prompt; `'none'` injects nothing (useful if\n * you supply your own instructions).\n */\n instructions: z.enum(['system', 'none']).optional(),\n\n /**\n * Validate emitted envelopes against the catalog. `'warn'` (default) logs a\n * warning and drops the offending block/envelope, keeping the rest of the\n * turn alive; `'strict'` throws on malformed JSON or unknown components (best\n * for development); `'off'` passes them through unchecked.\n *\n * IMPORTANT: this validates envelope structure and component *type names*\n * against the catalog only. It does NOT validate component props or\n * data-model values. Model-controlled values such as `Image.url` and `Text`\n * (inline Markdown, which the renderer may turn into HTML) pass through\n * untouched even under `'strict'`. `'strict'` is a well-formedness check, not\n * a security boundary — see the \"Security / trust boundary\" section of the\n * README. Prop sanitization is the renderer/catalog's responsibility, and\n * hosts should CSP-restrict image and other remote sources.\n */\n validate: z.enum(['strict', 'warn', 'off']).optional(),\n\n /**\n * Surface id policy. Provide a fixed id to reuse for every surface. Defaults\n * to a fresh UUID per surface.\n */\n surfaceId: z.string().optional(),\n\n /**\n * Protocol version stamped on emitted envelopes. Defaults to `'v0.9'`.\n * Constrained to the versions the renderer understands so a typo can't emit\n * envelopes that fail at runtime.\n */\n version: z\n .enum(SUPPORTED_VERSIONS as unknown as [string, ...string[]])\n .optional(),\n});\n\n/** Configuration for the {@link a2ui} middleware. */\nexport type A2uiOptions = z.infer<typeof A2uiOptionsSchema>;\n\n/** Type guard: is this part a text part? */\nfunction isTextPart(part: Part): part is TextPart {\n return (\n !!part &&\n typeof part === 'object' &&\n typeof (part as { text?: unknown }).text === 'string'\n );\n}\n\n/** Resolves the configured surface-id policy into a factory. */\nfunction surfaceIdFactory(policy: A2uiOptions['surfaceId']): () => string {\n if (typeof policy === 'string') return () => policy;\n // Use the Web Crypto API (available on Node >=16 and all supported \"exotic\"\n // runtimes, e.g. Cloudflare Workers) rather than a hard `node:crypto` import,\n // keeping the middleware portable.\n return () => globalThis.crypto.randomUUID();\n}\n\n/**\n * Wraps a surface-id factory so a single model turn's streamed parse and its\n * final-message parse mint the *same* surface ids.\n *\n * A turn is parsed twice: once incrementally over the streamed chunks, and once\n * over the aggregated final message (so consumers that read `response.message`\n * see a2ui parts too). Each parse pulls surface ids from the factory. With the\n * default `randomUUID` policy those two parses would otherwise produce different\n * ids for the same surface. So while streaming we generate and record ids in\n * order (`next`); before re-parsing the final message we `reset`, then\n * `replayNext` hands back the recorded ids in the same order (only generating a\n * fresh id if the final parse yields more blocks than the stream did).\n */\nfunction replayableSurfaceIds(base: () => string): {\n next: () => string;\n replayNext: () => string;\n reset: () => void;\n} {\n const generated: string[] = [];\n let cursor = 0;\n const next = () => {\n const id = base();\n generated.push(id);\n return id;\n };\n const replayNext = () =>\n cursor < generated.length ? generated[cursor++] : next();\n const reset = () => {\n cursor = 0;\n };\n return { next, replayNext, reset };\n}\n\n/**\n * Turns a parse result into ordered prose + a2ui parts, preserving the exact\n * source order (so prose after a block stays after it). Returns the new parts\n * to substitute for the original text part.\n */\nfunction partsFromParse(result: ParseResult): Part[] {\n const out: Part[] = [];\n for (const seg of result.segments) {\n if ('prose' in seg) {\n if (seg.prose) out.push({ text: seg.prose });\n } else {\n out.push(a2uiPart(seg.envelopes));\n }\n }\n return out;\n}\n\n/**\n * The A2UI generate middleware.\n *\n * @example\n * ```ts\n * import { a2ui } from '@genkit-ai/a2ui';\n *\n * export const uiAgent = ai.defineAgent({\n * name: 'uiAgent',\n * model: 'googleai/gemini-flash-latest',\n * system: 'You help users. Render UI when it is clearer than prose.',\n * use: [a2ui()], // defaults to the bundled 'basic' catalog\n * });\n * ```\n */\nexport const a2ui: GenerateMiddleware<typeof A2uiOptionsSchema> =\n generateMiddleware(\n {\n name: 'a2ui',\n description:\n 'Adds A2UI (Agent-to-UI) streaming UI support: injects catalog ' +\n 'capabilities into the prompt and rewrites emitted UI blocks into a2ui ' +\n 'data parts.',\n configSchema: A2uiOptionsSchema,\n },\n (options) => {\n const { ai, config } = options;\n const {\n catalog: catalogId = DEFAULT_CATALOG_ID,\n instructions = 'system',\n validate = 'warn',\n version = A2UI_VERSION,\n } = config ?? {};\n const nextSurfaceId = surfaceIdFactory(config?.surfaceId);\n\n return {\n model: async (req, ctx, next) => {\n // Resolve the catalog by id from the registry (falls back to the\n // bundled basic catalog for the default id).\n const catalog = await resolveCatalog(ai, catalogId);\n\n // Share surface ids between the streamed parse and the final-message\n // parse of this single turn, so the same surface gets the same id in\n // both (see replayableSurfaceIds).\n const surfaceIds = replayableSurfaceIds(nextSurfaceId);\n\n // 0) Sanitize any inbound a2ui data parts (e.g. a surface action sent\n // back as the next turn, or replayed history) into model-readable\n // text, so the underlying model's converter never sees the a2ui\n // mime type.\n const sanitized = sanitizeInboundA2ui(req);\n\n // 1) Inject catalog instructions into the system prompt.\n const request =\n instructions === 'none'\n ? sanitized\n : injectInstructions(sanitized, catalog);\n\n // 2) Wrap the streaming callback so streamed text is split into prose\n // deltas + whole a2ui parts as blocks complete.\n const streamParser = new A2uiStreamParser({\n catalog,\n validate,\n version,\n surfaceId: surfaceIds.next,\n });\n\n const originalOnChunk = ctx?.onChunk;\n const wrappedCtx = originalOnChunk\n ? {\n ...ctx,\n onChunk: (chunk: GenerateResponseChunkData) => {\n const transformed = transformChunk(chunk, streamParser);\n if (transformed) originalOnChunk(transformed);\n },\n }\n : ctx;\n\n // 3) Run downstream model, then flush the stream parser so the last\n // withheld prose tail (the parser holds back up to a partial\n // opening fence) and any unterminated trailing block still reach\n // the streaming consumer. Without this, clients that render purely\n // from stream deltas would show truncated prose / miss a final\n // block (the aggregated message recovers it, but the stream would\n // not).\n const response = await next(request, wrappedCtx);\n if (originalOnChunk) {\n const tail = partsFromParse(streamParser.flush());\n if (tail.length > 0) originalOnChunk({ content: tail });\n }\n\n // 4) Transform the final message. The final parse replays the same\n // surface ids the stream minted.\n surfaceIds.reset();\n return transformResponse(response, {\n catalog,\n validate,\n version,\n surfaceId: surfaceIds.replayNext,\n });\n },\n };\n }\n );\n\n/** Appends A2UI instructions to (or creates) the system message. */\nfunction injectInstructions(\n req: GenerateRequest,\n catalog: A2uiCatalog\n): GenerateRequest {\n const text = renderCatalogInstructions(catalog);\n const messages: MessageData[] = [...req.messages];\n const sysIdx = messages.findIndex((m) => m.role === 'system');\n if (sysIdx >= 0) {\n const sys = messages[sysIdx];\n messages[sysIdx] = {\n ...sys,\n content: [...sys.content, { text: '\\n\\n' + text }],\n };\n } else {\n messages.unshift({ role: 'system', content: [{ text }] });\n }\n return { ...req, messages };\n}\n\n/** Transforms a single streamed chunk; returns null if nothing to emit. */\nfunction transformChunk(\n chunk: GenerateResponseChunkData,\n parser: A2uiStreamParser\n): GenerateResponseChunkData | null {\n if (!chunk?.content || chunk.content.length === 0) return chunk;\n const newContent: Part[] = [];\n for (const part of chunk.content) {\n if (isTextPart(part) && part.text !== '') {\n newContent.push(...partsFromParse(parser.push(part.text)));\n } else {\n newContent.push(part);\n }\n }\n if (newContent.length === 0) return null;\n return { ...chunk, content: newContent };\n}\n\n/** Transforms the final response message: prose text + a2ui parts. */\nfunction transformResponse(\n response: GenerateResponseData,\n opts: {\n catalog: A2uiCatalog;\n validate: 'strict' | 'warn' | 'off';\n version: string;\n surfaceId: () => string;\n }\n): GenerateResponseData {\n const message = response.message;\n if (!message?.content) return response;\n\n const parser = new A2uiStreamParser(opts);\n const newContent: Part[] = [];\n for (const part of message.content) {\n if (isTextPart(part)) {\n // Combine the streamed-push and final-flush segments so ordering (prose\n // before/after a block) is preserved in the aggregated message too.\n const pushed = parser.push(part.text);\n const flushed = parser.flush();\n const segments = [...pushed.segments, ...flushed.segments];\n newContent.push(...partsFromParse({ segments } as ParseResult));\n } else {\n newContent.push(part);\n }\n }\n return {\n ...response,\n message: { ...message, content: newContent },\n };\n}\n\n/**\n * Converts inbound a2ui data parts in the request into model-readable text.\n *\n * The a2ui data part (mime `application/a2ui+json`) is meaningful to the client\n * renderer, but the underlying model's message converter (e.g. Gemini) does not\n * understand it. When a rendered surface's action is sent back as the next\n * turn's input — or when prior assistant turns containing surfaces are replayed\n * as history — we replace those parts with a compact text summary so the model\n * can reason about them.\n */\nfunction sanitizeInboundA2ui(req: GenerateRequest): GenerateRequest {\n let changed = false;\n const messages: MessageData[] = [];\n for (const message of req.messages) {\n if (!Array.isArray(message.content)) {\n messages.push(message);\n continue;\n }\n let msgChanged = false;\n const content: Part[] = [];\n for (const part of message.content) {\n if (isA2uiPart(part)) {\n msgChanged = true;\n const text = summarizeA2uiPart(part.data.envelopes);\n if (text) content.push({ text });\n } else {\n content.push(part as Part);\n }\n }\n if (!msgChanged) {\n messages.push(message);\n continue;\n }\n changed = true;\n // Drop a message that sanitizing emptied out. This happens when its only\n // content was an a2ui part whose envelopes all summarized to nothing (e.g.\n // an empty or all-unrecognized envelope array). Sending `content: []`\n // downstream would make providers like Gemini and Vertex reject the\n // request, so skip the message entirely instead.\n if (content.length === 0) continue;\n messages.push({ ...message, content });\n }\n return changed ? { ...req, messages } : req;\n}\n\n/**\n * Converts an array of a2ui envelopes from an inbound message part back into\n * model-readable text — the inverse of the outbound block-to-part transform.\n *\n * The two envelope kinds are handled differently on purpose:\n *\n * - Assistant-authored surface envelopes (`createSurface`, `updateComponents`,\n * `updateDataModel`, `deleteSurface`) are reconstructed as the canonical\n * `a2ui` fenced block the model originally emitted. Replaying a prior turn's\n * surface as history therefore shows the model its own UI output in the exact\n * format it is asked to produce, reinforcing correct behavior. (Summarizing it\n * to a sentinel like `[rendered UI surface]` instead taught the model to emit\n * that literal string in place of a real block.)\n * - Client-synthesized `action` envelopes never had a block form, so they\n * become a short text summary the model can reason about.\n *\n * Consecutive surface envelopes are grouped into a single block (one surface is\n * usually several envelopes: create + update(s)). Unknown envelope shapes are\n * dropped, so an all-unrecognized (or empty) envelope array summarizes to an\n * empty string; {@link sanitizeInboundA2ui} then drops the emptied message\n * rather than sending empty content downstream.\n */\nfunction summarizeA2uiPart(envelopes: A2uiEnvelope[]): string {\n const out: string[] = [];\n let pendingSurface: A2uiEnvelope[] = [];\n\n const flushSurface = () => {\n if (pendingSurface.length === 0) return;\n // Keep the real surface ids verbatim. The model may not reuse them for a\n // NEW surface: the parser forces a fresh id onto every `createSurface`\n // block (see `finalizeBlock`), so a copied id can't overwrite a prior\n // surface. Keeping the real ids lets the model correlate a replayed action\n // (`[UI action ... on surface <id>]`) with the surface it targeted — which\n // matters when several surfaces are on screen at once.\n //\n // Encode compactly (not pretty-printed): fewer tokens, and it collapses the\n // payload to a single line so the block is exactly three lines (open fence,\n // JSON, close fence). Because JSON escapes any newline inside a string as\n // `\\n`, an A2UI `Text` value containing a fenced code sample can't put a\n // literal ``` at the start of a line, so it can never prematurely close this\n // block (the parser's close fence is line-anchored). The block text can\n // still contain ``` characters mid-line; a fully robust emitter would use a\n // variable-length fence, but that also requires the parser's fixed\n // three-backtick open fence to become count-aware, so it is deferred.\n out.push('```a2ui\\n' + JSON.stringify(pendingSurface) + '\\n```');\n pendingSurface = [];\n };\n\n for (const env of envelopes) {\n if (!env || typeof env !== 'object') continue;\n if ('action' in env && env.action) {\n // Emit any buffered surface block before the action, preserving order.\n flushSurface();\n const a = env.action;\n const ctx =\n a.context && Object.keys(a.context).length\n ? ` context=${JSON.stringify(a.context)}`\n : '';\n out.push(`[UI action \"${a.name}\" on surface ${a.surfaceId}${ctx}]`);\n } else if (\n ('createSurface' in env && env.createSurface) ||\n ('updateComponents' in env && env.updateComponents) ||\n ('updateDataModel' in env && env.updateDataModel) ||\n ('deleteSurface' in env && env.deleteSurface)\n ) {\n pendingSurface.push(env);\n }\n }\n flushSurface();\n return out.join('\\n');\n}\n"],"mappings":"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","names":[]}