@genkit-ai/a2ui 0.5.0-rc.0 → 0.5.0-rc.2

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.
package/lib/client.d.mts CHANGED
@@ -1,5 +1,5 @@
1
- import { A2uiEnvelope, A2uiClientAction, A2uiPart } from './types.mjs';
2
- export { A2UI_MIME_TYPE, A2UI_VERSION, A2uiComponent, BASIC_CATALOG_ID } from './types.mjs';
1
+ import { A2uiServerEnvelope, A2uiClientAction, A2uiPart } from './types.mjs';
2
+ export { A2UI_MIME_TYPE, A2UI_VERSION, A2uiComponent, A2uiEnvelope, ActionEnvelope, BASIC_CATALOG_ID } from './types.mjs';
3
3
  export { a2uiEnvelopesFromParts, a2uiPart, isA2uiPart } from './part.mjs';
4
4
  import 'genkit';
5
5
 
@@ -9,7 +9,7 @@ type A2uiStreamEvent = {
9
9
  text: string;
10
10
  } | {
11
11
  type: 'envelopes';
12
- envelopes: A2uiEnvelope[];
12
+ envelopes: A2uiServerEnvelope[];
13
13
  };
14
14
  /** Options for {@link streamA2uiAgent}. */
15
15
  interface StreamA2uiAgentOptions {
@@ -50,10 +50,10 @@ declare function streamA2uiAgent(options: StreamA2uiAgentOptions): AsyncGenerato
50
50
  * part for richer handling.
51
51
  */
52
52
  declare function actionToMessage(action: A2uiClientAction): {
53
- role: string;
53
+ role: "user";
54
54
  content: (A2uiPart | {
55
55
  text: string;
56
56
  })[];
57
57
  };
58
58
 
59
- export { A2uiClientAction, A2uiEnvelope, A2uiPart, type A2uiStreamEvent, type StreamA2uiAgentOptions, actionToMessage, streamA2uiAgent };
59
+ export { A2uiClientAction, A2uiPart, A2uiServerEnvelope, type A2uiStreamEvent, type StreamA2uiAgentOptions, actionToMessage, streamA2uiAgent };
package/lib/client.d.ts CHANGED
@@ -1,5 +1,5 @@
1
- import { A2uiEnvelope, A2uiClientAction, A2uiPart } from './types.js';
2
- export { A2UI_MIME_TYPE, A2UI_VERSION, A2uiComponent, BASIC_CATALOG_ID } from './types.js';
1
+ import { A2uiServerEnvelope, A2uiClientAction, A2uiPart } from './types.js';
2
+ export { A2UI_MIME_TYPE, A2UI_VERSION, A2uiComponent, A2uiEnvelope, ActionEnvelope, BASIC_CATALOG_ID } from './types.js';
3
3
  export { a2uiEnvelopesFromParts, a2uiPart, isA2uiPart } from './part.js';
4
4
  import 'genkit';
5
5
 
@@ -9,7 +9,7 @@ type A2uiStreamEvent = {
9
9
  text: string;
10
10
  } | {
11
11
  type: 'envelopes';
12
- envelopes: A2uiEnvelope[];
12
+ envelopes: A2uiServerEnvelope[];
13
13
  };
14
14
  /** Options for {@link streamA2uiAgent}. */
15
15
  interface StreamA2uiAgentOptions {
@@ -50,10 +50,10 @@ declare function streamA2uiAgent(options: StreamA2uiAgentOptions): AsyncGenerato
50
50
  * part for richer handling.
51
51
  */
52
52
  declare function actionToMessage(action: A2uiClientAction): {
53
- role: string;
53
+ role: "user";
54
54
  content: (A2uiPart | {
55
55
  text: string;
56
56
  })[];
57
57
  };
58
58
 
59
- export { A2uiClientAction, A2uiEnvelope, A2uiPart, type A2uiStreamEvent, type StreamA2uiAgentOptions, actionToMessage, streamA2uiAgent };
59
+ export { A2uiClientAction, A2uiPart, A2uiServerEnvelope, type A2uiStreamEvent, type StreamA2uiAgentOptions, actionToMessage, streamA2uiAgent };
package/lib/client.js CHANGED
@@ -64,6 +64,8 @@ function actionToMessage(action) {
64
64
  }
65
65
  const summary = `User interacted with the UI (surface "${action.surfaceId}"): action "${action.name}"` + (action.context && Object.keys(action.context).length ? ` with context ${JSON.stringify(action.context)}` : "") + ".";
66
66
  return {
67
+ // `as const` keeps `role` as the literal `'user'` rather than widening to
68
+ // `string`, so the result is assignable to an agent input `message`.
67
69
  role: "user",
68
70
  content: [{ text: summary }, (0, import_part.a2uiPart)([{ action }])]
69
71
  };
package/lib/client.js.map CHANGED
@@ -1 +1 @@
1
- {"version":3,"sources":["../src/client.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 * Browser-safe client helpers for consuming an A2UI-enabled Genkit agent.\n *\n * This module has NO Node dependencies. It builds on Genkit's agent client\n * (`remoteAgent` from `genkit/beta/client`), filters A2UI parts out of the\n * agent stream, and hands whole envelopes to whatever renderer you use (e.g.\n * `@a2ui/web_core`'s `MessageProcessor`).\n *\n * @module\n */\n\nimport { remoteAgent } from 'genkit/beta/client';\nimport { a2uiEnvelopesFromParts, a2uiPart } from './part.js';\nimport type { A2uiClientAction, A2uiEnvelope } from './types.js';\n\nexport { a2uiEnvelopesFromParts, a2uiPart, isA2uiPart } from './part.js';\nexport {\n A2UI_MIME_TYPE,\n A2UI_VERSION,\n BASIC_CATALOG_ID,\n type A2uiClientAction,\n type A2uiComponent,\n type A2uiEnvelope,\n type A2uiPart,\n} from './types.js';\n\n/** A single event yielded while streaming an A2UI agent turn. */\nexport type A2uiStreamEvent =\n | { type: 'text'; text: string }\n | { type: 'envelopes'; envelopes: A2uiEnvelope[] };\n\n/** Options for {@link streamA2uiAgent}. */\nexport interface StreamA2uiAgentOptions {\n /** URL of the Genkit agent/flow endpoint (e.g. `/uiAgent`). */\n url: string;\n /** The user message to send (Genkit message data or a plain string). */\n message: string | Record<string, unknown>;\n /** Server-managed session id (a UUID). */\n sessionId?: string;\n /** Extra HTTP headers. */\n headers?: Record<string, string>;\n /** Abort signal to cancel the request. */\n abortSignal?: AbortSignal;\n}\n\n/** Normalizes a string message into a Genkit user message. */\nfunction toMessage(message: string | Record<string, unknown>) {\n if (typeof message === 'string') {\n return { role: 'user', content: [{ text: message }] };\n }\n return message;\n}\n\n/**\n * Streams an A2UI-enabled Genkit agent, yielding prose text deltas and complete\n * A2UI envelope batches in the order they arrive. Feed the envelopes to your\n * renderer's message processor.\n *\n * @example\n * ```ts\n * import { MessageProcessor } from '@a2ui/web_core/v0_9';\n * import { basicCatalog } from '@a2ui/lit/v0_9';\n * import { streamA2uiAgent } from '@genkit-ai/a2ui/client';\n *\n * const processor = new MessageProcessor([basicCatalog]);\n * for await (const ev of streamA2uiAgent({ url: '/uiAgent', message: 'weather in Tokyo' })) {\n * if (ev.type === 'text') appendProse(ev.text);\n * else processor.processMessages(ev.envelopes);\n * }\n * ```\n */\nexport async function* streamA2uiAgent(\n options: StreamA2uiAgentOptions\n): AsyncGenerator<A2uiStreamEvent, void, unknown> {\n const agent = remoteAgent({ url: options.url, headers: options.headers });\n // A server-managed session id (when provided) attaches this turn to an\n // existing conversation; otherwise the agent starts a fresh session.\n const chat = agent.chat(\n options.sessionId ? { sessionId: options.sessionId } : undefined\n );\n\n const turn = chat.sendStream(\n { message: toMessage(options.message) } as Parameters<\n typeof chat.sendStream\n >[0],\n { abortSignal: options.abortSignal }\n );\n\n for await (const chunk of turn.stream) {\n if (chunk.text) {\n yield { type: 'text', text: chunk.text };\n }\n // A2UI rides as data parts on the raw chunk; extract whole envelopes.\n const envelopes = a2uiEnvelopesFromParts(chunk.raw.modelChunk?.content);\n if (envelopes.length > 0) {\n yield { type: 'envelopes', envelopes };\n }\n }\n // Surface any server error / finalize the turn.\n await turn.response;\n}\n\n/**\n * Builds the agent input for sending a rendered surface's user action back to\n * the agent as the next turn. The action's `name` becomes the user message so\n * the agent can react to it; the full action object is attached as an a2ui data\n * part for richer handling.\n */\nexport function actionToMessage(action: A2uiClientAction) {\n if (!action) {\n throw new Error('actionToMessage: action is required');\n }\n const summary =\n `User interacted with the UI (surface \"${action.surfaceId}\"): ` +\n `action \"${action.name}\"` +\n (action.context && Object.keys(action.context).length\n ? ` with context ${JSON.stringify(action.context)}`\n : '') +\n '.';\n return {\n role: 'user',\n content: [{ text: summary }, a2uiPart([{ action }])],\n };\n}\n"],"mappings":";;;;;;;;;;;;;;;;;;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AA2BA,oBAA4B;AAC5B,kBAAiD;AAGjD,IAAAA,eAA6D;AAC7D,mBAQO;AAsBP,SAAS,UAAU,SAA2C;AAC5D,MAAI,OAAO,YAAY,UAAU;AAC/B,WAAO,EAAE,MAAM,QAAQ,SAAS,CAAC,EAAE,MAAM,QAAQ,CAAC,EAAE;AAAA,EACtD;AACA,SAAO;AACT;AAoBA,gBAAuB,gBACrB,SACgD;AAChD,QAAM,YAAQ,2BAAY,EAAE,KAAK,QAAQ,KAAK,SAAS,QAAQ,QAAQ,CAAC;AAGxE,QAAM,OAAO,MAAM;AAAA,IACjB,QAAQ,YAAY,EAAE,WAAW,QAAQ,UAAU,IAAI;AAAA,EACzD;AAEA,QAAM,OAAO,KAAK;AAAA,IAChB,EAAE,SAAS,UAAU,QAAQ,OAAO,EAAE;AAAA,IAGtC,EAAE,aAAa,QAAQ,YAAY;AAAA,EACrC;AAEA,mBAAiB,SAAS,KAAK,QAAQ;AACrC,QAAI,MAAM,MAAM;AACd,YAAM,EAAE,MAAM,QAAQ,MAAM,MAAM,KAAK;AAAA,IACzC;AAEA,UAAM,gBAAY,oCAAuB,MAAM,IAAI,YAAY,OAAO;AACtE,QAAI,UAAU,SAAS,GAAG;AACxB,YAAM,EAAE,MAAM,aAAa,UAAU;AAAA,IACvC;AAAA,EACF;AAEA,QAAM,KAAK;AACb;AAQO,SAAS,gBAAgB,QAA0B;AACxD,MAAI,CAAC,QAAQ;AACX,UAAM,IAAI,MAAM,qCAAqC;AAAA,EACvD;AACA,QAAM,UACJ,yCAAyC,OAAO,SAAS,eAC9C,OAAO,IAAI,OACrB,OAAO,WAAW,OAAO,KAAK,OAAO,OAAO,EAAE,SAC3C,iBAAiB,KAAK,UAAU,OAAO,OAAO,CAAC,KAC/C,MACJ;AACF,SAAO;AAAA,IACL,MAAM;AAAA,IACN,SAAS,CAAC,EAAE,MAAM,QAAQ,OAAG,sBAAS,CAAC,EAAE,OAAO,CAAC,CAAC,CAAC;AAAA,EACrD;AACF;","names":["import_part"]}
1
+ {"version":3,"sources":["../src/client.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 * Browser-safe client helpers for consuming an A2UI-enabled Genkit agent.\n *\n * This module has NO Node dependencies. It builds on Genkit's agent client\n * (`remoteAgent` from `genkit/beta/client`), filters A2UI parts out of the\n * agent stream, and hands whole envelopes to whatever renderer you use (e.g.\n * `@a2ui/web_core`'s `MessageProcessor`).\n *\n * @module\n */\n\nimport { remoteAgent } from 'genkit/beta/client';\nimport { a2uiEnvelopesFromParts, a2uiPart } from './part.js';\nimport type { A2uiClientAction, A2uiServerEnvelope } from './types.js';\n\nexport { a2uiEnvelopesFromParts, a2uiPart, isA2uiPart } from './part.js';\nexport {\n A2UI_MIME_TYPE,\n A2UI_VERSION,\n BASIC_CATALOG_ID,\n type A2uiClientAction,\n type A2uiComponent,\n type A2uiEnvelope,\n type A2uiPart,\n type A2uiServerEnvelope,\n type ActionEnvelope,\n} from './types.js';\n\n/** A single event yielded while streaming an A2UI agent turn. */\nexport type A2uiStreamEvent =\n | { type: 'text'; text: string }\n | { type: 'envelopes'; envelopes: A2uiServerEnvelope[] };\n\n/** Options for {@link streamA2uiAgent}. */\nexport interface StreamA2uiAgentOptions {\n /** URL of the Genkit agent/flow endpoint (e.g. `/uiAgent`). */\n url: string;\n /** The user message to send (Genkit message data or a plain string). */\n message: string | Record<string, unknown>;\n /** Server-managed session id (a UUID). */\n sessionId?: string;\n /** Extra HTTP headers. */\n headers?: Record<string, string>;\n /** Abort signal to cancel the request. */\n abortSignal?: AbortSignal;\n}\n\n/** Normalizes a string message into a Genkit user message. */\nfunction toMessage(message: string | Record<string, unknown>) {\n if (typeof message === 'string') {\n // `as const` keeps `role` as the literal `'user'` rather than widening to\n // `string`, so the result is assignable to an agent input `message`.\n return { role: 'user' as const, content: [{ text: message }] };\n }\n return message;\n}\n\n/**\n * Streams an A2UI-enabled Genkit agent, yielding prose text deltas and complete\n * A2UI envelope batches in the order they arrive. Feed the envelopes to your\n * renderer's message processor.\n *\n * @example\n * ```ts\n * import { MessageProcessor } from '@a2ui/web_core/v0_9';\n * import { basicCatalog } from '@a2ui/lit/v0_9';\n * import { streamA2uiAgent } from '@genkit-ai/a2ui/client';\n *\n * const processor = new MessageProcessor([basicCatalog]);\n * for await (const ev of streamA2uiAgent({ url: '/uiAgent', message: 'weather in Tokyo' })) {\n * if (ev.type === 'text') appendProse(ev.text);\n * else processor.processMessages(ev.envelopes);\n * }\n * ```\n */\nexport async function* streamA2uiAgent(\n options: StreamA2uiAgentOptions\n): AsyncGenerator<A2uiStreamEvent, void, unknown> {\n const agent = remoteAgent({ url: options.url, headers: options.headers });\n // A server-managed session id (when provided) attaches this turn to an\n // existing conversation; otherwise the agent starts a fresh session.\n const chat = agent.chat(\n options.sessionId ? { sessionId: options.sessionId } : undefined\n );\n\n const turn = chat.sendStream(\n { message: toMessage(options.message) } as Parameters<\n typeof chat.sendStream\n >[0],\n { abortSignal: options.abortSignal }\n );\n\n for await (const chunk of turn.stream) {\n if (chunk.text) {\n yield { type: 'text', text: chunk.text };\n }\n // A2UI rides as data parts on the raw chunk; extract whole envelopes.\n const envelopes = a2uiEnvelopesFromParts(chunk.raw.modelChunk?.content);\n if (envelopes.length > 0) {\n yield { type: 'envelopes', envelopes };\n }\n }\n // Surface any server error / finalize the turn.\n await turn.response;\n}\n\n/**\n * Builds the agent input for sending a rendered surface's user action back to\n * the agent as the next turn. The action's `name` becomes the user message so\n * the agent can react to it; the full action object is attached as an a2ui data\n * part for richer handling.\n */\nexport function actionToMessage(action: A2uiClientAction) {\n if (!action) {\n throw new Error('actionToMessage: action is required');\n }\n const summary =\n `User interacted with the UI (surface \"${action.surfaceId}\"): ` +\n `action \"${action.name}\"` +\n (action.context && Object.keys(action.context).length\n ? ` with context ${JSON.stringify(action.context)}`\n : '') +\n '.';\n return {\n // `as const` keeps `role` as the literal `'user'` rather than widening to\n // `string`, so the result is assignable to an agent input `message`.\n role: 'user' as const,\n content: [{ text: summary }, a2uiPart([{ action }])],\n };\n}\n"],"mappings":";;;;;;;;;;;;;;;;;;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AA2BA,oBAA4B;AAC5B,kBAAiD;AAGjD,IAAAA,eAA6D;AAC7D,mBAUO;AAsBP,SAAS,UAAU,SAA2C;AAC5D,MAAI,OAAO,YAAY,UAAU;AAG/B,WAAO,EAAE,MAAM,QAAiB,SAAS,CAAC,EAAE,MAAM,QAAQ,CAAC,EAAE;AAAA,EAC/D;AACA,SAAO;AACT;AAoBA,gBAAuB,gBACrB,SACgD;AAChD,QAAM,YAAQ,2BAAY,EAAE,KAAK,QAAQ,KAAK,SAAS,QAAQ,QAAQ,CAAC;AAGxE,QAAM,OAAO,MAAM;AAAA,IACjB,QAAQ,YAAY,EAAE,WAAW,QAAQ,UAAU,IAAI;AAAA,EACzD;AAEA,QAAM,OAAO,KAAK;AAAA,IAChB,EAAE,SAAS,UAAU,QAAQ,OAAO,EAAE;AAAA,IAGtC,EAAE,aAAa,QAAQ,YAAY;AAAA,EACrC;AAEA,mBAAiB,SAAS,KAAK,QAAQ;AACrC,QAAI,MAAM,MAAM;AACd,YAAM,EAAE,MAAM,QAAQ,MAAM,MAAM,KAAK;AAAA,IACzC;AAEA,UAAM,gBAAY,oCAAuB,MAAM,IAAI,YAAY,OAAO;AACtE,QAAI,UAAU,SAAS,GAAG;AACxB,YAAM,EAAE,MAAM,aAAa,UAAU;AAAA,IACvC;AAAA,EACF;AAEA,QAAM,KAAK;AACb;AAQO,SAAS,gBAAgB,QAA0B;AACxD,MAAI,CAAC,QAAQ;AACX,UAAM,IAAI,MAAM,qCAAqC;AAAA,EACvD;AACA,QAAM,UACJ,yCAAyC,OAAO,SAAS,eAC9C,OAAO,IAAI,OACrB,OAAO,WAAW,OAAO,KAAK,OAAO,OAAO,EAAE,SAC3C,iBAAiB,KAAK,UAAU,OAAO,OAAO,CAAC,KAC/C,MACJ;AACF,SAAO;AAAA;AAAA;AAAA,IAGL,MAAM;AAAA,IACN,SAAS,CAAC,EAAE,MAAM,QAAQ,OAAG,sBAAS,CAAC,EAAE,OAAO,CAAC,CAAC,CAAC;AAAA,EACrD;AACF;","names":["import_part"]}
package/lib/client.mjs CHANGED
@@ -38,6 +38,8 @@ function actionToMessage(action) {
38
38
  }
39
39
  const summary = `User interacted with the UI (surface "${action.surfaceId}"): action "${action.name}"` + (action.context && Object.keys(action.context).length ? ` with context ${JSON.stringify(action.context)}` : "") + ".";
40
40
  return {
41
+ // `as const` keeps `role` as the literal `'user'` rather than widening to
42
+ // `string`, so the result is assignable to an agent input `message`.
41
43
  role: "user",
42
44
  content: [{ text: summary }, a2uiPart([{ action }])]
43
45
  };
@@ -1 +1 @@
1
- {"version":3,"sources":["../src/client.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 * Browser-safe client helpers for consuming an A2UI-enabled Genkit agent.\n *\n * This module has NO Node dependencies. It builds on Genkit's agent client\n * (`remoteAgent` from `genkit/beta/client`), filters A2UI parts out of the\n * agent stream, and hands whole envelopes to whatever renderer you use (e.g.\n * `@a2ui/web_core`'s `MessageProcessor`).\n *\n * @module\n */\n\nimport { remoteAgent } from 'genkit/beta/client';\nimport { a2uiEnvelopesFromParts, a2uiPart } from './part.mjs';\nimport type { A2uiClientAction, A2uiEnvelope } from './types.mjs';\n\nexport { a2uiEnvelopesFromParts, a2uiPart, isA2uiPart } from './part.mjs';\nexport {\n A2UI_MIME_TYPE,\n A2UI_VERSION,\n BASIC_CATALOG_ID,\n type A2uiClientAction,\n type A2uiComponent,\n type A2uiEnvelope,\n type A2uiPart,\n} from './types.mjs';\n\n/** A single event yielded while streaming an A2UI agent turn. */\nexport type A2uiStreamEvent =\n | { type: 'text'; text: string }\n | { type: 'envelopes'; envelopes: A2uiEnvelope[] };\n\n/** Options for {@link streamA2uiAgent}. */\nexport interface StreamA2uiAgentOptions {\n /** URL of the Genkit agent/flow endpoint (e.g. `/uiAgent`). */\n url: string;\n /** The user message to send (Genkit message data or a plain string). */\n message: string | Record<string, unknown>;\n /** Server-managed session id (a UUID). */\n sessionId?: string;\n /** Extra HTTP headers. */\n headers?: Record<string, string>;\n /** Abort signal to cancel the request. */\n abortSignal?: AbortSignal;\n}\n\n/** Normalizes a string message into a Genkit user message. */\nfunction toMessage(message: string | Record<string, unknown>) {\n if (typeof message === 'string') {\n return { role: 'user', content: [{ text: message }] };\n }\n return message;\n}\n\n/**\n * Streams an A2UI-enabled Genkit agent, yielding prose text deltas and complete\n * A2UI envelope batches in the order they arrive. Feed the envelopes to your\n * renderer's message processor.\n *\n * @example\n * ```ts\n * import { MessageProcessor } from '@a2ui/web_core/v0_9';\n * import { basicCatalog } from '@a2ui/lit/v0_9';\n * import { streamA2uiAgent } from '@genkit-ai/a2ui/client';\n *\n * const processor = new MessageProcessor([basicCatalog]);\n * for await (const ev of streamA2uiAgent({ url: '/uiAgent', message: 'weather in Tokyo' })) {\n * if (ev.type === 'text') appendProse(ev.text);\n * else processor.processMessages(ev.envelopes);\n * }\n * ```\n */\nexport async function* streamA2uiAgent(\n options: StreamA2uiAgentOptions\n): AsyncGenerator<A2uiStreamEvent, void, unknown> {\n const agent = remoteAgent({ url: options.url, headers: options.headers });\n // A server-managed session id (when provided) attaches this turn to an\n // existing conversation; otherwise the agent starts a fresh session.\n const chat = agent.chat(\n options.sessionId ? { sessionId: options.sessionId } : undefined\n );\n\n const turn = chat.sendStream(\n { message: toMessage(options.message) } as Parameters<\n typeof chat.sendStream\n >[0],\n { abortSignal: options.abortSignal }\n );\n\n for await (const chunk of turn.stream) {\n if (chunk.text) {\n yield { type: 'text', text: chunk.text };\n }\n // A2UI rides as data parts on the raw chunk; extract whole envelopes.\n const envelopes = a2uiEnvelopesFromParts(chunk.raw.modelChunk?.content);\n if (envelopes.length > 0) {\n yield { type: 'envelopes', envelopes };\n }\n }\n // Surface any server error / finalize the turn.\n await turn.response;\n}\n\n/**\n * Builds the agent input for sending a rendered surface's user action back to\n * the agent as the next turn. The action's `name` becomes the user message so\n * the agent can react to it; the full action object is attached as an a2ui data\n * part for richer handling.\n */\nexport function actionToMessage(action: A2uiClientAction) {\n if (!action) {\n throw new Error('actionToMessage: action is required');\n }\n const summary =\n `User interacted with the UI (surface \"${action.surfaceId}\"): ` +\n `action \"${action.name}\"` +\n (action.context && Object.keys(action.context).length\n ? ` with context ${JSON.stringify(action.context)}`\n : '') +\n '.';\n return {\n role: 'user',\n content: [{ text: summary }, a2uiPart([{ action }])],\n };\n}\n"],"mappings":"AA2BA,SAAS,mBAAmB;AAC5B,SAAS,wBAAwB,gBAAgB;AAGjD,SAAS,0BAAAA,yBAAwB,YAAAC,WAAU,kBAAkB;AAC7D;AAAA,EACE;AAAA,EACA;AAAA,EACA;AAAA,OAKK;AAsBP,SAAS,UAAU,SAA2C;AAC5D,MAAI,OAAO,YAAY,UAAU;AAC/B,WAAO,EAAE,MAAM,QAAQ,SAAS,CAAC,EAAE,MAAM,QAAQ,CAAC,EAAE;AAAA,EACtD;AACA,SAAO;AACT;AAoBA,gBAAuB,gBACrB,SACgD;AAChD,QAAM,QAAQ,YAAY,EAAE,KAAK,QAAQ,KAAK,SAAS,QAAQ,QAAQ,CAAC;AAGxE,QAAM,OAAO,MAAM;AAAA,IACjB,QAAQ,YAAY,EAAE,WAAW,QAAQ,UAAU,IAAI;AAAA,EACzD;AAEA,QAAM,OAAO,KAAK;AAAA,IAChB,EAAE,SAAS,UAAU,QAAQ,OAAO,EAAE;AAAA,IAGtC,EAAE,aAAa,QAAQ,YAAY;AAAA,EACrC;AAEA,mBAAiB,SAAS,KAAK,QAAQ;AACrC,QAAI,MAAM,MAAM;AACd,YAAM,EAAE,MAAM,QAAQ,MAAM,MAAM,KAAK;AAAA,IACzC;AAEA,UAAM,YAAY,uBAAuB,MAAM,IAAI,YAAY,OAAO;AACtE,QAAI,UAAU,SAAS,GAAG;AACxB,YAAM,EAAE,MAAM,aAAa,UAAU;AAAA,IACvC;AAAA,EACF;AAEA,QAAM,KAAK;AACb;AAQO,SAAS,gBAAgB,QAA0B;AACxD,MAAI,CAAC,QAAQ;AACX,UAAM,IAAI,MAAM,qCAAqC;AAAA,EACvD;AACA,QAAM,UACJ,yCAAyC,OAAO,SAAS,eAC9C,OAAO,IAAI,OACrB,OAAO,WAAW,OAAO,KAAK,OAAO,OAAO,EAAE,SAC3C,iBAAiB,KAAK,UAAU,OAAO,OAAO,CAAC,KAC/C,MACJ;AACF,SAAO;AAAA,IACL,MAAM;AAAA,IACN,SAAS,CAAC,EAAE,MAAM,QAAQ,GAAG,SAAS,CAAC,EAAE,OAAO,CAAC,CAAC,CAAC;AAAA,EACrD;AACF;","names":["a2uiEnvelopesFromParts","a2uiPart"]}
1
+ {"version":3,"sources":["../src/client.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 * Browser-safe client helpers for consuming an A2UI-enabled Genkit agent.\n *\n * This module has NO Node dependencies. It builds on Genkit's agent client\n * (`remoteAgent` from `genkit/beta/client`), filters A2UI parts out of the\n * agent stream, and hands whole envelopes to whatever renderer you use (e.g.\n * `@a2ui/web_core`'s `MessageProcessor`).\n *\n * @module\n */\n\nimport { remoteAgent } from 'genkit/beta/client';\nimport { a2uiEnvelopesFromParts, a2uiPart } from './part.mjs';\nimport type { A2uiClientAction, A2uiServerEnvelope } from './types.mjs';\n\nexport { a2uiEnvelopesFromParts, a2uiPart, isA2uiPart } from './part.mjs';\nexport {\n A2UI_MIME_TYPE,\n A2UI_VERSION,\n BASIC_CATALOG_ID,\n type A2uiClientAction,\n type A2uiComponent,\n type A2uiEnvelope,\n type A2uiPart,\n type A2uiServerEnvelope,\n type ActionEnvelope,\n} from './types.mjs';\n\n/** A single event yielded while streaming an A2UI agent turn. */\nexport type A2uiStreamEvent =\n | { type: 'text'; text: string }\n | { type: 'envelopes'; envelopes: A2uiServerEnvelope[] };\n\n/** Options for {@link streamA2uiAgent}. */\nexport interface StreamA2uiAgentOptions {\n /** URL of the Genkit agent/flow endpoint (e.g. `/uiAgent`). */\n url: string;\n /** The user message to send (Genkit message data or a plain string). */\n message: string | Record<string, unknown>;\n /** Server-managed session id (a UUID). */\n sessionId?: string;\n /** Extra HTTP headers. */\n headers?: Record<string, string>;\n /** Abort signal to cancel the request. */\n abortSignal?: AbortSignal;\n}\n\n/** Normalizes a string message into a Genkit user message. */\nfunction toMessage(message: string | Record<string, unknown>) {\n if (typeof message === 'string') {\n // `as const` keeps `role` as the literal `'user'` rather than widening to\n // `string`, so the result is assignable to an agent input `message`.\n return { role: 'user' as const, content: [{ text: message }] };\n }\n return message;\n}\n\n/**\n * Streams an A2UI-enabled Genkit agent, yielding prose text deltas and complete\n * A2UI envelope batches in the order they arrive. Feed the envelopes to your\n * renderer's message processor.\n *\n * @example\n * ```ts\n * import { MessageProcessor } from '@a2ui/web_core/v0_9';\n * import { basicCatalog } from '@a2ui/lit/v0_9';\n * import { streamA2uiAgent } from '@genkit-ai/a2ui/client';\n *\n * const processor = new MessageProcessor([basicCatalog]);\n * for await (const ev of streamA2uiAgent({ url: '/uiAgent', message: 'weather in Tokyo' })) {\n * if (ev.type === 'text') appendProse(ev.text);\n * else processor.processMessages(ev.envelopes);\n * }\n * ```\n */\nexport async function* streamA2uiAgent(\n options: StreamA2uiAgentOptions\n): AsyncGenerator<A2uiStreamEvent, void, unknown> {\n const agent = remoteAgent({ url: options.url, headers: options.headers });\n // A server-managed session id (when provided) attaches this turn to an\n // existing conversation; otherwise the agent starts a fresh session.\n const chat = agent.chat(\n options.sessionId ? { sessionId: options.sessionId } : undefined\n );\n\n const turn = chat.sendStream(\n { message: toMessage(options.message) } as Parameters<\n typeof chat.sendStream\n >[0],\n { abortSignal: options.abortSignal }\n );\n\n for await (const chunk of turn.stream) {\n if (chunk.text) {\n yield { type: 'text', text: chunk.text };\n }\n // A2UI rides as data parts on the raw chunk; extract whole envelopes.\n const envelopes = a2uiEnvelopesFromParts(chunk.raw.modelChunk?.content);\n if (envelopes.length > 0) {\n yield { type: 'envelopes', envelopes };\n }\n }\n // Surface any server error / finalize the turn.\n await turn.response;\n}\n\n/**\n * Builds the agent input for sending a rendered surface's user action back to\n * the agent as the next turn. The action's `name` becomes the user message so\n * the agent can react to it; the full action object is attached as an a2ui data\n * part for richer handling.\n */\nexport function actionToMessage(action: A2uiClientAction) {\n if (!action) {\n throw new Error('actionToMessage: action is required');\n }\n const summary =\n `User interacted with the UI (surface \"${action.surfaceId}\"): ` +\n `action \"${action.name}\"` +\n (action.context && Object.keys(action.context).length\n ? ` with context ${JSON.stringify(action.context)}`\n : '') +\n '.';\n return {\n // `as const` keeps `role` as the literal `'user'` rather than widening to\n // `string`, so the result is assignable to an agent input `message`.\n role: 'user' as const,\n content: [{ text: summary }, a2uiPart([{ action }])],\n };\n}\n"],"mappings":"AA2BA,SAAS,mBAAmB;AAC5B,SAAS,wBAAwB,gBAAgB;AAGjD,SAAS,0BAAAA,yBAAwB,YAAAC,WAAU,kBAAkB;AAC7D;AAAA,EACE;AAAA,EACA;AAAA,EACA;AAAA,OAOK;AAsBP,SAAS,UAAU,SAA2C;AAC5D,MAAI,OAAO,YAAY,UAAU;AAG/B,WAAO,EAAE,MAAM,QAAiB,SAAS,CAAC,EAAE,MAAM,QAAQ,CAAC,EAAE;AAAA,EAC/D;AACA,SAAO;AACT;AAoBA,gBAAuB,gBACrB,SACgD;AAChD,QAAM,QAAQ,YAAY,EAAE,KAAK,QAAQ,KAAK,SAAS,QAAQ,QAAQ,CAAC;AAGxE,QAAM,OAAO,MAAM;AAAA,IACjB,QAAQ,YAAY,EAAE,WAAW,QAAQ,UAAU,IAAI;AAAA,EACzD;AAEA,QAAM,OAAO,KAAK;AAAA,IAChB,EAAE,SAAS,UAAU,QAAQ,OAAO,EAAE;AAAA,IAGtC,EAAE,aAAa,QAAQ,YAAY;AAAA,EACrC;AAEA,mBAAiB,SAAS,KAAK,QAAQ;AACrC,QAAI,MAAM,MAAM;AACd,YAAM,EAAE,MAAM,QAAQ,MAAM,MAAM,KAAK;AAAA,IACzC;AAEA,UAAM,YAAY,uBAAuB,MAAM,IAAI,YAAY,OAAO;AACtE,QAAI,UAAU,SAAS,GAAG;AACxB,YAAM,EAAE,MAAM,aAAa,UAAU;AAAA,IACvC;AAAA,EACF;AAEA,QAAM,KAAK;AACb;AAQO,SAAS,gBAAgB,QAA0B;AACxD,MAAI,CAAC,QAAQ;AACX,UAAM,IAAI,MAAM,qCAAqC;AAAA,EACvD;AACA,QAAM,UACJ,yCAAyC,OAAO,SAAS,eAC9C,OAAO,IAAI,OACrB,OAAO,WAAW,OAAO,KAAK,OAAO,OAAO,EAAE,SAC3C,iBAAiB,KAAK,UAAU,OAAO,OAAO,CAAC,KAC/C,MACJ;AACF,SAAO;AAAA;AAAA;AAAA,IAGL,MAAM;AAAA,IACN,SAAS,CAAC,EAAE,MAAM,QAAQ,GAAG,SAAS,CAAC,EAAE,OAAO,CAAC,CAAC,CAAC;AAAA,EACrD;AACF;","names":["a2uiEnvelopesFromParts","a2uiPart"]}
package/lib/index.d.mts CHANGED
@@ -3,6 +3,6 @@ export { LoadCatalogOptions, loadCatalog } from './loader.mjs';
3
3
  export { A2uiOptions, A2uiOptionsSchema, a2ui } from './middleware.mjs';
4
4
  export { A2uiParserOptions, A2uiStreamParser, ParseResult } from './parser.mjs';
5
5
  export { a2uiEnvelopesFromParts, a2uiPart, isA2uiPart } from './part.mjs';
6
- export { A2UI_MIME_TYPE, A2UI_VERSION, A2uiClientAction, A2uiComponent, A2uiEnvelope, A2uiPart, BASIC_CATALOG_ID, CreateSurfaceEnvelope, DeleteSurfaceEnvelope, UpdateComponentsEnvelope, UpdateDataModelEnvelope } from './types.mjs';
6
+ export { A2UI_MIME_TYPE, A2UI_VERSION, A2uiClientAction, A2uiComponent, A2uiEnvelope, A2uiPart, A2uiServerEnvelope, ActionEnvelope, BASIC_CATALOG_ID, CreateSurfaceEnvelope, DeleteSurfaceEnvelope, UpdateComponentsEnvelope, UpdateDataModelEnvelope } from './types.mjs';
7
7
  import 'genkit/registry';
8
8
  import 'genkit';
package/lib/index.d.ts CHANGED
@@ -3,6 +3,6 @@ export { LoadCatalogOptions, loadCatalog } from './loader.js';
3
3
  export { A2uiOptions, A2uiOptionsSchema, a2ui } from './middleware.js';
4
4
  export { A2uiParserOptions, A2uiStreamParser, ParseResult } from './parser.js';
5
5
  export { a2uiEnvelopesFromParts, a2uiPart, isA2uiPart } from './part.js';
6
- export { A2UI_MIME_TYPE, A2UI_VERSION, A2uiClientAction, A2uiComponent, A2uiEnvelope, A2uiPart, BASIC_CATALOG_ID, CreateSurfaceEnvelope, DeleteSurfaceEnvelope, UpdateComponentsEnvelope, UpdateDataModelEnvelope } from './types.js';
6
+ export { A2UI_MIME_TYPE, A2UI_VERSION, A2uiClientAction, A2uiComponent, A2uiEnvelope, A2uiPart, A2uiServerEnvelope, ActionEnvelope, BASIC_CATALOG_ID, CreateSurfaceEnvelope, DeleteSurfaceEnvelope, UpdateComponentsEnvelope, UpdateDataModelEnvelope } from './types.js';
7
7
  import 'genkit/registry';
8
8
  import 'genkit';
package/lib/index.js.map CHANGED
@@ -1 +1 @@
1
- {"version":3,"sources":["../src/index.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 * `@genkit-ai/a2ui` — A2UI (Agent-to-UI) streaming UI protocol support for\n * Genkit agents.\n *\n * The whole server-side integration is the {@link a2ui} model middleware; add it\n * to `defineAgent({ use: [...] })` or `ai.generate({ use: [...] })`. Pair it with\n * {@link basicCatalog} (or your own catalog) and render on the client with the\n * `@a2ui/lit` renderer plus the helpers in `@genkit-ai/a2ui/client`.\n *\n * @module\n */\n\nexport {\n A2UI_CATALOG_VALUE_TYPE,\n DEFAULT_CATALOG_ID,\n SURFACE_ID_PLACEHOLDER,\n basicCatalog,\n renderCatalogInstructions,\n type A2uiCatalog,\n type A2uiCatalogComponent,\n} from './catalog.js';\nexport { loadCatalog, type LoadCatalogOptions } from './loader.js';\nexport { A2uiOptionsSchema, a2ui, type A2uiOptions } from './middleware.js';\nexport {\n A2uiStreamParser,\n type A2uiParserOptions,\n type ParseResult,\n} from './parser.js';\nexport { a2uiEnvelopesFromParts, a2uiPart, isA2uiPart } from './part.js';\nexport {\n A2UI_MIME_TYPE,\n A2UI_VERSION,\n BASIC_CATALOG_ID,\n type A2uiClientAction,\n type A2uiComponent,\n type A2uiEnvelope,\n type A2uiPart,\n type CreateSurfaceEnvelope,\n type DeleteSurfaceEnvelope,\n type UpdateComponentsEnvelope,\n type UpdateDataModelEnvelope,\n} from './types.js';\n"],"mappings":";;;;;;;;;;;;;;;;;;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AA4BA,qBAQO;AACP,oBAAqD;AACrD,wBAA0D;AAC1D,oBAIO;AACP,kBAA6D;AAC7D,mBAYO;","names":[]}
1
+ {"version":3,"sources":["../src/index.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 * `@genkit-ai/a2ui` — A2UI (Agent-to-UI) streaming UI protocol support for\n * Genkit agents.\n *\n * The whole server-side integration is the {@link a2ui} model middleware; add it\n * to `defineAgent({ use: [...] })` or `ai.generate({ use: [...] })`. Pair it with\n * {@link basicCatalog} (or your own catalog) and render on the client with the\n * `@a2ui/lit` renderer plus the helpers in `@genkit-ai/a2ui/client`.\n *\n * @module\n */\n\nexport {\n A2UI_CATALOG_VALUE_TYPE,\n DEFAULT_CATALOG_ID,\n SURFACE_ID_PLACEHOLDER,\n basicCatalog,\n renderCatalogInstructions,\n type A2uiCatalog,\n type A2uiCatalogComponent,\n} from './catalog.js';\nexport { loadCatalog, type LoadCatalogOptions } from './loader.js';\nexport { A2uiOptionsSchema, a2ui, type A2uiOptions } from './middleware.js';\nexport {\n A2uiStreamParser,\n type A2uiParserOptions,\n type ParseResult,\n} from './parser.js';\nexport { a2uiEnvelopesFromParts, a2uiPart, isA2uiPart } from './part.js';\nexport {\n A2UI_MIME_TYPE,\n A2UI_VERSION,\n BASIC_CATALOG_ID,\n type A2uiClientAction,\n type A2uiComponent,\n type A2uiEnvelope,\n type A2uiPart,\n type A2uiServerEnvelope,\n type ActionEnvelope,\n type CreateSurfaceEnvelope,\n type DeleteSurfaceEnvelope,\n type UpdateComponentsEnvelope,\n type UpdateDataModelEnvelope,\n} from './types.js';\n"],"mappings":";;;;;;;;;;;;;;;;;;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AA4BA,qBAQO;AACP,oBAAqD;AACrD,wBAA0D;AAC1D,oBAIO;AACP,kBAA6D;AAC7D,mBAcO;","names":[]}
package/lib/index.mjs.map CHANGED
@@ -1 +1 @@
1
- {"version":3,"sources":["../src/index.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 * `@genkit-ai/a2ui` — A2UI (Agent-to-UI) streaming UI protocol support for\n * Genkit agents.\n *\n * The whole server-side integration is the {@link a2ui} model middleware; add it\n * to `defineAgent({ use: [...] })` or `ai.generate({ use: [...] })`. Pair it with\n * {@link basicCatalog} (or your own catalog) and render on the client with the\n * `@a2ui/lit` renderer plus the helpers in `@genkit-ai/a2ui/client`.\n *\n * @module\n */\n\nexport {\n A2UI_CATALOG_VALUE_TYPE,\n DEFAULT_CATALOG_ID,\n SURFACE_ID_PLACEHOLDER,\n basicCatalog,\n renderCatalogInstructions,\n type A2uiCatalog,\n type A2uiCatalogComponent,\n} from './catalog.mjs';\nexport { loadCatalog, type LoadCatalogOptions } from './loader.mjs';\nexport { A2uiOptionsSchema, a2ui, type A2uiOptions } from './middleware.mjs';\nexport {\n A2uiStreamParser,\n type A2uiParserOptions,\n type ParseResult,\n} from './parser.mjs';\nexport { a2uiEnvelopesFromParts, a2uiPart, isA2uiPart } from './part.mjs';\nexport {\n A2UI_MIME_TYPE,\n A2UI_VERSION,\n BASIC_CATALOG_ID,\n type A2uiClientAction,\n type A2uiComponent,\n type A2uiEnvelope,\n type A2uiPart,\n type CreateSurfaceEnvelope,\n type DeleteSurfaceEnvelope,\n type UpdateComponentsEnvelope,\n type UpdateDataModelEnvelope,\n} from './types.mjs';\n"],"mappings":"AA4BA;AAAA,EACE;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,OAGK;AACP,SAAS,mBAA4C;AACrD,SAAS,mBAAmB,YAA8B;AAC1D;AAAA,EACE;AAAA,OAGK;AACP,SAAS,wBAAwB,UAAU,kBAAkB;AAC7D;AAAA,EACE;AAAA,EACA;AAAA,EACA;AAAA,OASK;","names":[]}
1
+ {"version":3,"sources":["../src/index.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 * `@genkit-ai/a2ui` — A2UI (Agent-to-UI) streaming UI protocol support for\n * Genkit agents.\n *\n * The whole server-side integration is the {@link a2ui} model middleware; add it\n * to `defineAgent({ use: [...] })` or `ai.generate({ use: [...] })`. Pair it with\n * {@link basicCatalog} (or your own catalog) and render on the client with the\n * `@a2ui/lit` renderer plus the helpers in `@genkit-ai/a2ui/client`.\n *\n * @module\n */\n\nexport {\n A2UI_CATALOG_VALUE_TYPE,\n DEFAULT_CATALOG_ID,\n SURFACE_ID_PLACEHOLDER,\n basicCatalog,\n renderCatalogInstructions,\n type A2uiCatalog,\n type A2uiCatalogComponent,\n} from './catalog.mjs';\nexport { loadCatalog, type LoadCatalogOptions } from './loader.mjs';\nexport { A2uiOptionsSchema, a2ui, type A2uiOptions } from './middleware.mjs';\nexport {\n A2uiStreamParser,\n type A2uiParserOptions,\n type ParseResult,\n} from './parser.mjs';\nexport { a2uiEnvelopesFromParts, a2uiPart, isA2uiPart } from './part.mjs';\nexport {\n A2UI_MIME_TYPE,\n A2UI_VERSION,\n BASIC_CATALOG_ID,\n type A2uiClientAction,\n type A2uiComponent,\n type A2uiEnvelope,\n type A2uiPart,\n type A2uiServerEnvelope,\n type ActionEnvelope,\n type CreateSurfaceEnvelope,\n type DeleteSurfaceEnvelope,\n type UpdateComponentsEnvelope,\n type UpdateDataModelEnvelope,\n} from './types.mjs';\n"],"mappings":"AA4BA;AAAA,EACE;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,OAGK;AACP,SAAS,mBAA4C;AACrD,SAAS,mBAAmB,YAA8B;AAC1D;AAAA,EACE;AAAA,OAGK;AACP,SAAS,wBAAwB,UAAU,kBAAkB;AAC7D;AAAA,EACE;AAAA,EACA;AAAA,EACA;AAAA,OAWK;","names":[]}
@@ -78,17 +78,17 @@ declare const A2uiOptionsSchema: z.ZodObject<{
78
78
  */
79
79
  version: z.ZodOptional<z.ZodEnum<[string, ...string[]]>>;
80
80
  }, "strip", z.ZodTypeAny, {
81
+ version?: string | undefined;
82
+ surfaceId?: string | undefined;
81
83
  catalog?: string | undefined;
82
84
  instructions?: "system" | "none" | undefined;
83
85
  validate?: "strict" | "warn" | "off" | undefined;
84
- surfaceId?: string | undefined;
85
- version?: string | undefined;
86
86
  }, {
87
+ version?: string | undefined;
88
+ surfaceId?: string | undefined;
87
89
  catalog?: string | undefined;
88
90
  instructions?: "system" | "none" | undefined;
89
91
  validate?: "strict" | "warn" | "off" | undefined;
90
- surfaceId?: string | undefined;
91
- version?: string | undefined;
92
92
  }>;
93
93
  /** Configuration for the {@link a2ui} middleware. */
94
94
  type A2uiOptions = z.infer<typeof A2uiOptionsSchema>;
@@ -78,17 +78,17 @@ declare const A2uiOptionsSchema: z.ZodObject<{
78
78
  */
79
79
  version: z.ZodOptional<z.ZodEnum<[string, ...string[]]>>;
80
80
  }, "strip", z.ZodTypeAny, {
81
+ version?: string | undefined;
82
+ surfaceId?: string | undefined;
81
83
  catalog?: string | undefined;
82
84
  instructions?: "system" | "none" | undefined;
83
85
  validate?: "strict" | "warn" | "off" | undefined;
84
- surfaceId?: string | undefined;
85
- version?: string | undefined;
86
86
  }, {
87
+ version?: string | undefined;
88
+ surfaceId?: string | undefined;
87
89
  catalog?: string | undefined;
88
90
  instructions?: "system" | "none" | undefined;
89
91
  validate?: "strict" | "warn" | "off" | undefined;
90
- surfaceId?: string | undefined;
91
- version?: string | undefined;
92
92
  }>;
93
93
  /** Configuration for the {@link a2ui} middleware. */
94
94
  type A2uiOptions = z.infer<typeof A2uiOptionsSchema>;
package/lib/middleware.js CHANGED
@@ -181,24 +181,34 @@ function transformChunk(chunk, parser) {
181
181
  return { ...chunk, content: newContent };
182
182
  }
183
183
  function transformResponse(response, opts) {
184
- const message = response.message;
184
+ const message = response.message ?? response.candidates?.[0]?.message;
185
185
  if (!message?.content) return response;
186
186
  const parser = new import_parser.A2uiStreamParser(opts);
187
187
  const newContent = [];
188
+ const flushHeld = () => {
189
+ const tail = parser.flush();
190
+ newContent.push(...partsFromParse(tail));
191
+ };
188
192
  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 }));
193
+ if (isTextPart(part) && part.text !== "") {
194
+ newContent.push(...partsFromParse(parser.push(part.text)));
194
195
  } else {
196
+ flushHeld();
195
197
  newContent.push(part);
196
198
  }
197
199
  }
198
- return {
199
- ...response,
200
- message: { ...message, content: newContent }
201
- };
200
+ flushHeld();
201
+ const newMessage = { ...message, content: newContent };
202
+ const result = { ...response };
203
+ if (result.message) {
204
+ result.message = newMessage;
205
+ }
206
+ if (result.candidates?.[0]) {
207
+ result.candidates = result.candidates.map(
208
+ (c, i) => i === 0 ? { ...c, message: newMessage } : c
209
+ );
210
+ }
211
+ return result;
202
212
  }
203
213
  function sanitizeInboundA2ui(req) {
204
214
  let changed = false;
@@ -1 +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":[]}
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 // Real models (e.g. google-genai) return the candidates shape, not a\n // top-level `message`; only the GenerateResponse constructor later collapses\n // `message ?? candidates[0].message`. Read from whichever the model used so\n // the transform runs on the real path, not just when a caller hands us a\n // pre-collapsed `message`.\n const message = response.message ?? response.candidates?.[0]?.message;\n if (!message?.content) return response;\n\n const parser = new A2uiStreamParser(opts);\n\n const newContent: Part[] = [];\n\n // Drains whatever the parser is still holding (a withheld prose tail, or an\n // unterminated trailing block) and appends it. Called at every non-text\n // boundary and once at the end.\n const flushHeld = () => {\n const tail = parser.flush();\n newContent.push(...partsFromParse(tail));\n };\n\n for (const part of message.content) {\n if (isTextPart(part) && part.text !== '') {\n // Push WITHOUT flushing between consecutive text parts so an a2ui block\n // that spans several adjacent text parts is stitched back together. The\n // model's final message is not guaranteed to coalesce adjacent text: the\n // Gemini plugin, for instance, aggregates a turn into many separate text\n // parts (fence, JSON body split many ways, close fence, then a trailing\n // empty-text part carrying the thought signature), so a per-part flush\n // would reset the parser mid-block and leak the whole surface back out as\n // raw prose. This mirrors the streaming path, which shares one parser\n // across all chunks and flushes only once at the end.\n newContent.push(...partsFromParse(parser.push(part.text)));\n } else {\n // A non-text part (e.g. a toolRequest) or an empty-text part (e.g. the\n // trailing thought-signature carrier) is a boundary: flush any held tail\n // so it lands before this part, preserving order. This is what a\n // tool-calling turn [text(\"Checking the weather.\"), toolRequest] relies\n // on so the prose is not reordered behind the toolRequest. Then carry the\n // part through untouched so its metadata survives.\n flushHeld();\n newContent.push(part);\n }\n }\n flushHeld();\n\n const newMessage = { ...message, content: newContent };\n\n // Write the transformed message back to WHICHEVER shape(s) the response\n // carries, so no consumer sees the raw prose. Real providers (e.g.\n // google-genai) use the candidates shape, but a response could carry a\n // top-level `message` and a `candidates` array at once (e.g. a prior\n // middleware pre-populated `message` while keeping `candidates`); updating\n // only one would leave the other holding the untransformed fence text. Only\n // candidate 0 is transformed: the framework collapses to\n // `candidates[0].message` and candidateCount defaults to 1, and each\n // candidate would otherwise consume the shared surface-id replay state.\n const result = { ...response };\n if (result.message) {\n result.message = newMessage;\n }\n if (result.candidates?.[0]) {\n result.candidates = result.candidates.map((c, i) =>\n i === 0 ? { ...c, message: newMessage } : c\n );\n }\n return result;\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":[]}
@@ -163,24 +163,34 @@ function transformChunk(chunk, parser) {
163
163
  return { ...chunk, content: newContent };
164
164
  }
165
165
  function transformResponse(response, opts) {
166
- const message = response.message;
166
+ const message = response.message ?? response.candidates?.[0]?.message;
167
167
  if (!message?.content) return response;
168
168
  const parser = new A2uiStreamParser(opts);
169
169
  const newContent = [];
170
+ const flushHeld = () => {
171
+ const tail = parser.flush();
172
+ newContent.push(...partsFromParse(tail));
173
+ };
170
174
  for (const part of message.content) {
171
- if (isTextPart(part)) {
172
- const pushed = parser.push(part.text);
173
- const flushed = parser.flush();
174
- const segments = [...pushed.segments, ...flushed.segments];
175
- newContent.push(...partsFromParse({ segments }));
175
+ if (isTextPart(part) && part.text !== "") {
176
+ newContent.push(...partsFromParse(parser.push(part.text)));
176
177
  } else {
178
+ flushHeld();
177
179
  newContent.push(part);
178
180
  }
179
181
  }
180
- return {
181
- ...response,
182
- message: { ...message, content: newContent }
183
- };
182
+ flushHeld();
183
+ const newMessage = { ...message, content: newContent };
184
+ const result = { ...response };
185
+ if (result.message) {
186
+ result.message = newMessage;
187
+ }
188
+ if (result.candidates?.[0]) {
189
+ result.candidates = result.candidates.map(
190
+ (c, i) => i === 0 ? { ...c, message: newMessage } : c
191
+ );
192
+ }
193
+ return result;
184
194
  }
185
195
  function sanitizeInboundA2ui(req) {
186
196
  let changed = false;
@@ -1 +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.mjs';\nimport { resolveCatalog } from './loader.mjs';\nimport { A2uiStreamParser, type ParseResult } from './parser.mjs';\nimport { a2uiPart, isA2uiPart } from './part.mjs';\nimport {\n A2UI_VERSION,\n SUPPORTED_VERSIONS,\n type A2uiEnvelope,\n} from './types.mjs';\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":[]}
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.mjs';\nimport { resolveCatalog } from './loader.mjs';\nimport { A2uiStreamParser, type ParseResult } from './parser.mjs';\nimport { a2uiPart, isA2uiPart } from './part.mjs';\nimport {\n A2UI_VERSION,\n SUPPORTED_VERSIONS,\n type A2uiEnvelope,\n} from './types.mjs';\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 // Real models (e.g. google-genai) return the candidates shape, not a\n // top-level `message`; only the GenerateResponse constructor later collapses\n // `message ?? candidates[0].message`. Read from whichever the model used so\n // the transform runs on the real path, not just when a caller hands us a\n // pre-collapsed `message`.\n const message = response.message ?? response.candidates?.[0]?.message;\n if (!message?.content) return response;\n\n const parser = new A2uiStreamParser(opts);\n\n const newContent: Part[] = [];\n\n // Drains whatever the parser is still holding (a withheld prose tail, or an\n // unterminated trailing block) and appends it. Called at every non-text\n // boundary and once at the end.\n const flushHeld = () => {\n const tail = parser.flush();\n newContent.push(...partsFromParse(tail));\n };\n\n for (const part of message.content) {\n if (isTextPart(part) && part.text !== '') {\n // Push WITHOUT flushing between consecutive text parts so an a2ui block\n // that spans several adjacent text parts is stitched back together. The\n // model's final message is not guaranteed to coalesce adjacent text: the\n // Gemini plugin, for instance, aggregates a turn into many separate text\n // parts (fence, JSON body split many ways, close fence, then a trailing\n // empty-text part carrying the thought signature), so a per-part flush\n // would reset the parser mid-block and leak the whole surface back out as\n // raw prose. This mirrors the streaming path, which shares one parser\n // across all chunks and flushes only once at the end.\n newContent.push(...partsFromParse(parser.push(part.text)));\n } else {\n // A non-text part (e.g. a toolRequest) or an empty-text part (e.g. the\n // trailing thought-signature carrier) is a boundary: flush any held tail\n // so it lands before this part, preserving order. This is what a\n // tool-calling turn [text(\"Checking the weather.\"), toolRequest] relies\n // on so the prose is not reordered behind the toolRequest. Then carry the\n // part through untouched so its metadata survives.\n flushHeld();\n newContent.push(part);\n }\n }\n flushHeld();\n\n const newMessage = { ...message, content: newContent };\n\n // Write the transformed message back to WHICHEVER shape(s) the response\n // carries, so no consumer sees the raw prose. Real providers (e.g.\n // google-genai) use the candidates shape, but a response could carry a\n // top-level `message` and a `candidates` array at once (e.g. a prior\n // middleware pre-populated `message` while keeping `candidates`); updating\n // only one would leave the other holding the untransformed fence text. Only\n // candidate 0 is transformed: the framework collapses to\n // `candidates[0].message` and candidateCount defaults to 1, and each\n // candidate would otherwise consume the shared surface-id replay state.\n const result = { ...response };\n if (result.message) {\n result.message = newMessage;\n }\n if (result.candidates?.[0]) {\n result.candidates = result.candidates.map((c, i) =>\n i === 0 ? { ...c, message: newMessage } : c\n );\n }\n return result;\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":[]}
package/lib/part.d.mts CHANGED
@@ -1,5 +1,5 @@
1
1
  import { Part } from 'genkit';
2
- import { A2uiEnvelope, A2uiPart } from './types.mjs';
2
+ import { A2uiServerEnvelope, A2uiEnvelope, A2uiPart } from './types.mjs';
3
3
 
4
4
  /**
5
5
  * Copyright 2026 Google LLC
@@ -17,7 +17,7 @@ import { A2uiEnvelope, A2uiPart } from './types.mjs';
17
17
  * limitations under the License.
18
18
  */
19
19
  /**
20
- * Helpers for working with the canonical "a2ui part" — a Genkit `data` part
20
+ * Helpers for working with the canonical "a2ui part" - a Genkit `data` part
21
21
  * whose `data` is an object `{ envelopes }` wrapping an array of A2UI envelopes,
22
22
  * tagged with {@link A2UI_MIME_TYPE}.
23
23
  *
@@ -50,7 +50,13 @@ declare function isA2uiPart(part: unknown): part is A2uiPart;
50
50
  * be `undefined`); a nullish list is treated as empty.
51
51
  *
52
52
  * Returns `[]` for content that carries no a2ui parts (e.g. plain prose).
53
+ *
54
+ * The result is typed as {@link A2uiServerEnvelope}[] - the surfaces the agent
55
+ * renders - because that is all a render stream carries. Any inbound
56
+ * {@link ActionEnvelope} (a user action echoed in history) is filtered out, so
57
+ * the returned envelopes can be handed straight to a renderer's message
58
+ * processor without a cast.
53
59
  */
54
- declare function a2uiEnvelopesFromParts(parts: readonly Part[] | null | undefined): A2uiEnvelope[];
60
+ declare function a2uiEnvelopesFromParts(parts: readonly Part[] | null | undefined): A2uiServerEnvelope[];
55
61
 
56
62
  export { a2uiEnvelopesFromParts, a2uiPart, isA2uiPart };
package/lib/part.d.ts CHANGED
@@ -1,5 +1,5 @@
1
1
  import { Part } from 'genkit';
2
- import { A2uiEnvelope, A2uiPart } from './types.js';
2
+ import { A2uiServerEnvelope, A2uiEnvelope, A2uiPart } from './types.js';
3
3
 
4
4
  /**
5
5
  * Copyright 2026 Google LLC
@@ -17,7 +17,7 @@ import { A2uiEnvelope, A2uiPart } from './types.js';
17
17
  * limitations under the License.
18
18
  */
19
19
  /**
20
- * Helpers for working with the canonical "a2ui part" — a Genkit `data` part
20
+ * Helpers for working with the canonical "a2ui part" - a Genkit `data` part
21
21
  * whose `data` is an object `{ envelopes }` wrapping an array of A2UI envelopes,
22
22
  * tagged with {@link A2UI_MIME_TYPE}.
23
23
  *
@@ -50,7 +50,13 @@ declare function isA2uiPart(part: unknown): part is A2uiPart;
50
50
  * be `undefined`); a nullish list is treated as empty.
51
51
  *
52
52
  * Returns `[]` for content that carries no a2ui parts (e.g. plain prose).
53
+ *
54
+ * The result is typed as {@link A2uiServerEnvelope}[] - the surfaces the agent
55
+ * renders - because that is all a render stream carries. Any inbound
56
+ * {@link ActionEnvelope} (a user action echoed in history) is filtered out, so
57
+ * the returned envelopes can be handed straight to a renderer's message
58
+ * processor without a cast.
53
59
  */
54
- declare function a2uiEnvelopesFromParts(parts: readonly Part[] | null | undefined): A2uiEnvelope[];
60
+ declare function a2uiEnvelopesFromParts(parts: readonly Part[] | null | undefined): A2uiServerEnvelope[];
55
61
 
56
62
  export { a2uiEnvelopesFromParts, a2uiPart, isA2uiPart };
package/lib/part.js CHANGED
@@ -39,10 +39,23 @@ function a2uiEnvelopesFromParts(parts) {
39
39
  if (!Array.isArray(parts)) return [];
40
40
  const out = [];
41
41
  for (const part of parts) {
42
- if (isA2uiPart(part)) out.push(...part.data.envelopes);
42
+ if (!isA2uiPart(part)) continue;
43
+ for (const env of part.data.envelopes) {
44
+ if (isServerEnvelope(env)) out.push(env);
45
+ }
43
46
  }
44
47
  return out;
45
48
  }
49
+ const SERVER_ENVELOPE_KEYS = [
50
+ "createSurface",
51
+ "updateComponents",
52
+ "updateDataModel",
53
+ "deleteSurface"
54
+ ];
55
+ function isServerEnvelope(env) {
56
+ if (!env || typeof env !== "object" || Array.isArray(env)) return false;
57
+ return SERVER_ENVELOPE_KEYS.some((key) => key in env);
58
+ }
46
59
  // Annotate the CommonJS export names for ESM import in node:
47
60
  0 && (module.exports = {
48
61
  a2uiEnvelopesFromParts,
package/lib/part.js.map CHANGED
@@ -1 +1 @@
1
- {"version":3,"sources":["../src/part.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 * Helpers for working with the canonical \"a2ui part\" — a Genkit `data` part\n * whose `data` is an object `{ envelopes }` wrapping an array of A2UI envelopes,\n * tagged with {@link A2UI_MIME_TYPE}.\n *\n * These helpers operate on plain part-shaped objects (no Genkit runtime import),\n * so they are safe to use on both the server and the browser. To pull envelopes\n * off an agent stream, pass the chunk's parts:\n * `a2uiEnvelopesFromParts(chunk.raw.modelChunk?.content)`.\n *\n * @module\n */\n\nimport { type Part } from 'genkit';\nimport { A2UI_MIME_TYPE, type A2uiEnvelope, type A2uiPart } from './types.js';\n\n/** A minimal structural view of a Genkit part for these helpers. */\ninterface PartLike {\n data?: unknown;\n metadata?: Record<string, unknown> | null;\n [key: string]: unknown;\n}\n\n/** Builds an a2ui data part wrapping the given envelopes. */\nexport function a2uiPart(envelopes: A2uiEnvelope[]): A2uiPart {\n return {\n data: { envelopes },\n metadata: { mimeType: A2UI_MIME_TYPE },\n };\n}\n\n/** Type guard: is this part an a2ui data part? */\nexport function isA2uiPart(part: unknown): part is A2uiPart {\n const p = part as PartLike | null | undefined;\n const data = p?.data as { envelopes?: unknown } | undefined;\n return (\n !!p &&\n typeof p === 'object' &&\n !!data &&\n typeof data === 'object' &&\n Array.isArray(data.envelopes) &&\n (p as PartLike).metadata?.mimeType === A2UI_MIME_TYPE\n );\n}\n\n/**\n * Extracts all A2UI envelopes carried by the given `parts`.\n *\n * This is the single entry point for reading envelopes off any part-carrying\n * value: pass a message's, chunk's, or response's `content`. For example, to\n * consume an agent stream:\n *\n * ```ts\n * for await (const chunk of turn.stream) {\n * const envelopes = a2uiEnvelopesFromParts(chunk.raw.modelChunk?.content);\n * }\n * ```\n *\n * `parts` is nullable purely for call-site convenience (a chunk's content can\n * be `undefined`); a nullish list is treated as empty.\n *\n * Returns `[]` for content that carries no a2ui parts (e.g. plain prose).\n */\nexport function a2uiEnvelopesFromParts(\n parts: readonly Part[] | null | undefined\n): A2uiEnvelope[] {\n if (!Array.isArray(parts)) return [];\n const out: A2uiEnvelope[] = [];\n for (const part of parts) {\n if (isA2uiPart(part)) out.push(...part.data.envelopes);\n }\n return out;\n}\n"],"mappings":";;;;;;;;;;;;;;;;;;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AA8BA,mBAAiE;AAU1D,SAAS,SAAS,WAAqC;AAC5D,SAAO;AAAA,IACL,MAAM,EAAE,UAAU;AAAA,IAClB,UAAU,EAAE,UAAU,4BAAe;AAAA,EACvC;AACF;AAGO,SAAS,WAAW,MAAiC;AAC1D,QAAM,IAAI;AACV,QAAM,OAAO,GAAG;AAChB,SACE,CAAC,CAAC,KACF,OAAO,MAAM,YACb,CAAC,CAAC,QACF,OAAO,SAAS,YAChB,MAAM,QAAQ,KAAK,SAAS,KAC3B,EAAe,UAAU,aAAa;AAE3C;AAoBO,SAAS,uBACd,OACgB;AAChB,MAAI,CAAC,MAAM,QAAQ,KAAK,EAAG,QAAO,CAAC;AACnC,QAAM,MAAsB,CAAC;AAC7B,aAAW,QAAQ,OAAO;AACxB,QAAI,WAAW,IAAI,EAAG,KAAI,KAAK,GAAG,KAAK,KAAK,SAAS;AAAA,EACvD;AACA,SAAO;AACT;","names":[]}
1
+ {"version":3,"sources":["../src/part.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 * Helpers for working with the canonical \"a2ui part\" - a Genkit `data` part\n * whose `data` is an object `{ envelopes }` wrapping an array of A2UI envelopes,\n * tagged with {@link A2UI_MIME_TYPE}.\n *\n * These helpers operate on plain part-shaped objects (no Genkit runtime import),\n * so they are safe to use on both the server and the browser. To pull envelopes\n * off an agent stream, pass the chunk's parts:\n * `a2uiEnvelopesFromParts(chunk.raw.modelChunk?.content)`.\n *\n * @module\n */\n\nimport { type Part } from 'genkit';\nimport {\n A2UI_MIME_TYPE,\n type A2uiEnvelope,\n type A2uiPart,\n type A2uiServerEnvelope,\n type CreateSurfaceEnvelope,\n type DeleteSurfaceEnvelope,\n type UpdateComponentsEnvelope,\n type UpdateDataModelEnvelope,\n} from './types.js';\n\n/** A minimal structural view of a Genkit part for these helpers. */\ninterface PartLike {\n data?: unknown;\n metadata?: Record<string, unknown> | null;\n [key: string]: unknown;\n}\n\n/** Builds an a2ui data part wrapping the given envelopes. */\nexport function a2uiPart(envelopes: A2uiEnvelope[]): A2uiPart {\n return {\n data: { envelopes },\n metadata: { mimeType: A2UI_MIME_TYPE },\n };\n}\n\n/** Type guard: is this part an a2ui data part? */\nexport function isA2uiPart(part: unknown): part is A2uiPart {\n const p = part as PartLike | null | undefined;\n const data = p?.data as { envelopes?: unknown } | undefined;\n return (\n !!p &&\n typeof p === 'object' &&\n !!data &&\n typeof data === 'object' &&\n Array.isArray(data.envelopes) &&\n (p as PartLike).metadata?.mimeType === A2UI_MIME_TYPE\n );\n}\n\n/**\n * Extracts all A2UI envelopes carried by the given `parts`.\n *\n * This is the single entry point for reading envelopes off any part-carrying\n * value: pass a message's, chunk's, or response's `content`. For example, to\n * consume an agent stream:\n *\n * ```ts\n * for await (const chunk of turn.stream) {\n * const envelopes = a2uiEnvelopesFromParts(chunk.raw.modelChunk?.content);\n * }\n * ```\n *\n * `parts` is nullable purely for call-site convenience (a chunk's content can\n * be `undefined`); a nullish list is treated as empty.\n *\n * Returns `[]` for content that carries no a2ui parts (e.g. plain prose).\n *\n * The result is typed as {@link A2uiServerEnvelope}[] - the surfaces the agent\n * renders - because that is all a render stream carries. Any inbound\n * {@link ActionEnvelope} (a user action echoed in history) is filtered out, so\n * the returned envelopes can be handed straight to a renderer's message\n * processor without a cast.\n */\nexport function a2uiEnvelopesFromParts(\n parts: readonly Part[] | null | undefined\n): A2uiServerEnvelope[] {\n if (!Array.isArray(parts)) return [];\n const out: A2uiServerEnvelope[] = [];\n for (const part of parts) {\n if (!isA2uiPart(part)) continue;\n for (const env of part.data.envelopes) {\n // Keep only server-to-client surface envelopes. This is a positive check,\n // so inbound action envelopes (a user action echoed in history) and any\n // null / non-object / array / arbitrary-object garbage from malformed\n // model output are all excluded, keeping the `A2uiServerEnvelope[]`\n // return type honest.\n if (isServerEnvelope(env)) out.push(env);\n }\n }\n return out;\n}\n\n/**\n * The discriminant key each server-to-client surface envelope carries (every\n * variant is `{ version } & { <key>: ... }`). Deriving this from the union keeps\n * {@link SERVER_ENVELOPE_KEYS} honest: a typo or a stale key stops compiling.\n */\ntype ServerEnvelopeKey =\n | Exclude<keyof CreateSurfaceEnvelope, 'version'>\n | Exclude<keyof UpdateComponentsEnvelope, 'version'>\n | Exclude<keyof UpdateDataModelEnvelope, 'version'>\n | Exclude<keyof DeleteSurfaceEnvelope, 'version'>;\n\n/**\n * The discriminant keys of the server-to-client surface envelopes. The\n * `satisfies` guard validates every entry against {@link ServerEnvelopeKey}, so\n * an invalid or misspelled key is a compile error rather than a silently dropped\n * envelope at runtime.\n */\nconst SERVER_ENVELOPE_KEYS = [\n 'createSurface',\n 'updateComponents',\n 'updateDataModel',\n 'deleteSurface',\n] as const satisfies readonly ServerEnvelopeKey[];\n\n/**\n * Narrows a value to a server-to-client surface envelope by checking for one of\n * the known surface discriminant keys. Arrays are excluded explicitly since\n * `typeof [] === 'object'`.\n */\nfunction isServerEnvelope(env: unknown): env is A2uiServerEnvelope {\n if (!env || typeof env !== 'object' || Array.isArray(env)) return false;\n return SERVER_ENVELOPE_KEYS.some((key) => key in env);\n}\n"],"mappings":";;;;;;;;;;;;;;;;;;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AA8BA,mBASO;AAUA,SAAS,SAAS,WAAqC;AAC5D,SAAO;AAAA,IACL,MAAM,EAAE,UAAU;AAAA,IAClB,UAAU,EAAE,UAAU,4BAAe;AAAA,EACvC;AACF;AAGO,SAAS,WAAW,MAAiC;AAC1D,QAAM,IAAI;AACV,QAAM,OAAO,GAAG;AAChB,SACE,CAAC,CAAC,KACF,OAAO,MAAM,YACb,CAAC,CAAC,QACF,OAAO,SAAS,YAChB,MAAM,QAAQ,KAAK,SAAS,KAC3B,EAAe,UAAU,aAAa;AAE3C;AA0BO,SAAS,uBACd,OACsB;AACtB,MAAI,CAAC,MAAM,QAAQ,KAAK,EAAG,QAAO,CAAC;AACnC,QAAM,MAA4B,CAAC;AACnC,aAAW,QAAQ,OAAO;AACxB,QAAI,CAAC,WAAW,IAAI,EAAG;AACvB,eAAW,OAAO,KAAK,KAAK,WAAW;AAMrC,UAAI,iBAAiB,GAAG,EAAG,KAAI,KAAK,GAAG;AAAA,IACzC;AAAA,EACF;AACA,SAAO;AACT;AAmBA,MAAM,uBAAuB;AAAA,EAC3B;AAAA,EACA;AAAA,EACA;AAAA,EACA;AACF;AAOA,SAAS,iBAAiB,KAAyC;AACjE,MAAI,CAAC,OAAO,OAAO,QAAQ,YAAY,MAAM,QAAQ,GAAG,EAAG,QAAO;AAClE,SAAO,qBAAqB,KAAK,CAAC,QAAQ,OAAO,GAAG;AACtD;","names":[]}
package/lib/part.mjs CHANGED
@@ -1,4 +1,6 @@
1
- import { A2UI_MIME_TYPE } from "./types.mjs";
1
+ import {
2
+ A2UI_MIME_TYPE
3
+ } from "./types.mjs";
2
4
  function a2uiPart(envelopes) {
3
5
  return {
4
6
  data: { envelopes },
@@ -14,10 +16,23 @@ function a2uiEnvelopesFromParts(parts) {
14
16
  if (!Array.isArray(parts)) return [];
15
17
  const out = [];
16
18
  for (const part of parts) {
17
- if (isA2uiPart(part)) out.push(...part.data.envelopes);
19
+ if (!isA2uiPart(part)) continue;
20
+ for (const env of part.data.envelopes) {
21
+ if (isServerEnvelope(env)) out.push(env);
22
+ }
18
23
  }
19
24
  return out;
20
25
  }
26
+ const SERVER_ENVELOPE_KEYS = [
27
+ "createSurface",
28
+ "updateComponents",
29
+ "updateDataModel",
30
+ "deleteSurface"
31
+ ];
32
+ function isServerEnvelope(env) {
33
+ if (!env || typeof env !== "object" || Array.isArray(env)) return false;
34
+ return SERVER_ENVELOPE_KEYS.some((key) => key in env);
35
+ }
21
36
  export {
22
37
  a2uiEnvelopesFromParts,
23
38
  a2uiPart,
package/lib/part.mjs.map CHANGED
@@ -1 +1 @@
1
- {"version":3,"sources":["../src/part.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 * Helpers for working with the canonical \"a2ui part\" — a Genkit `data` part\n * whose `data` is an object `{ envelopes }` wrapping an array of A2UI envelopes,\n * tagged with {@link A2UI_MIME_TYPE}.\n *\n * These helpers operate on plain part-shaped objects (no Genkit runtime import),\n * so they are safe to use on both the server and the browser. To pull envelopes\n * off an agent stream, pass the chunk's parts:\n * `a2uiEnvelopesFromParts(chunk.raw.modelChunk?.content)`.\n *\n * @module\n */\n\nimport { type Part } from 'genkit';\nimport { A2UI_MIME_TYPE, type A2uiEnvelope, type A2uiPart } from './types.mjs';\n\n/** A minimal structural view of a Genkit part for these helpers. */\ninterface PartLike {\n data?: unknown;\n metadata?: Record<string, unknown> | null;\n [key: string]: unknown;\n}\n\n/** Builds an a2ui data part wrapping the given envelopes. */\nexport function a2uiPart(envelopes: A2uiEnvelope[]): A2uiPart {\n return {\n data: { envelopes },\n metadata: { mimeType: A2UI_MIME_TYPE },\n };\n}\n\n/** Type guard: is this part an a2ui data part? */\nexport function isA2uiPart(part: unknown): part is A2uiPart {\n const p = part as PartLike | null | undefined;\n const data = p?.data as { envelopes?: unknown } | undefined;\n return (\n !!p &&\n typeof p === 'object' &&\n !!data &&\n typeof data === 'object' &&\n Array.isArray(data.envelopes) &&\n (p as PartLike).metadata?.mimeType === A2UI_MIME_TYPE\n );\n}\n\n/**\n * Extracts all A2UI envelopes carried by the given `parts`.\n *\n * This is the single entry point for reading envelopes off any part-carrying\n * value: pass a message's, chunk's, or response's `content`. For example, to\n * consume an agent stream:\n *\n * ```ts\n * for await (const chunk of turn.stream) {\n * const envelopes = a2uiEnvelopesFromParts(chunk.raw.modelChunk?.content);\n * }\n * ```\n *\n * `parts` is nullable purely for call-site convenience (a chunk's content can\n * be `undefined`); a nullish list is treated as empty.\n *\n * Returns `[]` for content that carries no a2ui parts (e.g. plain prose).\n */\nexport function a2uiEnvelopesFromParts(\n parts: readonly Part[] | null | undefined\n): A2uiEnvelope[] {\n if (!Array.isArray(parts)) return [];\n const out: A2uiEnvelope[] = [];\n for (const part of parts) {\n if (isA2uiPart(part)) out.push(...part.data.envelopes);\n }\n return out;\n}\n"],"mappings":"AA8BA,SAAS,sBAAwD;AAU1D,SAAS,SAAS,WAAqC;AAC5D,SAAO;AAAA,IACL,MAAM,EAAE,UAAU;AAAA,IAClB,UAAU,EAAE,UAAU,eAAe;AAAA,EACvC;AACF;AAGO,SAAS,WAAW,MAAiC;AAC1D,QAAM,IAAI;AACV,QAAM,OAAO,GAAG;AAChB,SACE,CAAC,CAAC,KACF,OAAO,MAAM,YACb,CAAC,CAAC,QACF,OAAO,SAAS,YAChB,MAAM,QAAQ,KAAK,SAAS,KAC3B,EAAe,UAAU,aAAa;AAE3C;AAoBO,SAAS,uBACd,OACgB;AAChB,MAAI,CAAC,MAAM,QAAQ,KAAK,EAAG,QAAO,CAAC;AACnC,QAAM,MAAsB,CAAC;AAC7B,aAAW,QAAQ,OAAO;AACxB,QAAI,WAAW,IAAI,EAAG,KAAI,KAAK,GAAG,KAAK,KAAK,SAAS;AAAA,EACvD;AACA,SAAO;AACT;","names":[]}
1
+ {"version":3,"sources":["../src/part.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 * Helpers for working with the canonical \"a2ui part\" - a Genkit `data` part\n * whose `data` is an object `{ envelopes }` wrapping an array of A2UI envelopes,\n * tagged with {@link A2UI_MIME_TYPE}.\n *\n * These helpers operate on plain part-shaped objects (no Genkit runtime import),\n * so they are safe to use on both the server and the browser. To pull envelopes\n * off an agent stream, pass the chunk's parts:\n * `a2uiEnvelopesFromParts(chunk.raw.modelChunk?.content)`.\n *\n * @module\n */\n\nimport { type Part } from 'genkit';\nimport {\n A2UI_MIME_TYPE,\n type A2uiEnvelope,\n type A2uiPart,\n type A2uiServerEnvelope,\n type CreateSurfaceEnvelope,\n type DeleteSurfaceEnvelope,\n type UpdateComponentsEnvelope,\n type UpdateDataModelEnvelope,\n} from './types.mjs';\n\n/** A minimal structural view of a Genkit part for these helpers. */\ninterface PartLike {\n data?: unknown;\n metadata?: Record<string, unknown> | null;\n [key: string]: unknown;\n}\n\n/** Builds an a2ui data part wrapping the given envelopes. */\nexport function a2uiPart(envelopes: A2uiEnvelope[]): A2uiPart {\n return {\n data: { envelopes },\n metadata: { mimeType: A2UI_MIME_TYPE },\n };\n}\n\n/** Type guard: is this part an a2ui data part? */\nexport function isA2uiPart(part: unknown): part is A2uiPart {\n const p = part as PartLike | null | undefined;\n const data = p?.data as { envelopes?: unknown } | undefined;\n return (\n !!p &&\n typeof p === 'object' &&\n !!data &&\n typeof data === 'object' &&\n Array.isArray(data.envelopes) &&\n (p as PartLike).metadata?.mimeType === A2UI_MIME_TYPE\n );\n}\n\n/**\n * Extracts all A2UI envelopes carried by the given `parts`.\n *\n * This is the single entry point for reading envelopes off any part-carrying\n * value: pass a message's, chunk's, or response's `content`. For example, to\n * consume an agent stream:\n *\n * ```ts\n * for await (const chunk of turn.stream) {\n * const envelopes = a2uiEnvelopesFromParts(chunk.raw.modelChunk?.content);\n * }\n * ```\n *\n * `parts` is nullable purely for call-site convenience (a chunk's content can\n * be `undefined`); a nullish list is treated as empty.\n *\n * Returns `[]` for content that carries no a2ui parts (e.g. plain prose).\n *\n * The result is typed as {@link A2uiServerEnvelope}[] - the surfaces the agent\n * renders - because that is all a render stream carries. Any inbound\n * {@link ActionEnvelope} (a user action echoed in history) is filtered out, so\n * the returned envelopes can be handed straight to a renderer's message\n * processor without a cast.\n */\nexport function a2uiEnvelopesFromParts(\n parts: readonly Part[] | null | undefined\n): A2uiServerEnvelope[] {\n if (!Array.isArray(parts)) return [];\n const out: A2uiServerEnvelope[] = [];\n for (const part of parts) {\n if (!isA2uiPart(part)) continue;\n for (const env of part.data.envelopes) {\n // Keep only server-to-client surface envelopes. This is a positive check,\n // so inbound action envelopes (a user action echoed in history) and any\n // null / non-object / array / arbitrary-object garbage from malformed\n // model output are all excluded, keeping the `A2uiServerEnvelope[]`\n // return type honest.\n if (isServerEnvelope(env)) out.push(env);\n }\n }\n return out;\n}\n\n/**\n * The discriminant key each server-to-client surface envelope carries (every\n * variant is `{ version } & { <key>: ... }`). Deriving this from the union keeps\n * {@link SERVER_ENVELOPE_KEYS} honest: a typo or a stale key stops compiling.\n */\ntype ServerEnvelopeKey =\n | Exclude<keyof CreateSurfaceEnvelope, 'version'>\n | Exclude<keyof UpdateComponentsEnvelope, 'version'>\n | Exclude<keyof UpdateDataModelEnvelope, 'version'>\n | Exclude<keyof DeleteSurfaceEnvelope, 'version'>;\n\n/**\n * The discriminant keys of the server-to-client surface envelopes. The\n * `satisfies` guard validates every entry against {@link ServerEnvelopeKey}, so\n * an invalid or misspelled key is a compile error rather than a silently dropped\n * envelope at runtime.\n */\nconst SERVER_ENVELOPE_KEYS = [\n 'createSurface',\n 'updateComponents',\n 'updateDataModel',\n 'deleteSurface',\n] as const satisfies readonly ServerEnvelopeKey[];\n\n/**\n * Narrows a value to a server-to-client surface envelope by checking for one of\n * the known surface discriminant keys. Arrays are excluded explicitly since\n * `typeof [] === 'object'`.\n */\nfunction isServerEnvelope(env: unknown): env is A2uiServerEnvelope {\n if (!env || typeof env !== 'object' || Array.isArray(env)) return false;\n return SERVER_ENVELOPE_KEYS.some((key) => key in env);\n}\n"],"mappings":"AA8BA;AAAA,EACE;AAAA,OAQK;AAUA,SAAS,SAAS,WAAqC;AAC5D,SAAO;AAAA,IACL,MAAM,EAAE,UAAU;AAAA,IAClB,UAAU,EAAE,UAAU,eAAe;AAAA,EACvC;AACF;AAGO,SAAS,WAAW,MAAiC;AAC1D,QAAM,IAAI;AACV,QAAM,OAAO,GAAG;AAChB,SACE,CAAC,CAAC,KACF,OAAO,MAAM,YACb,CAAC,CAAC,QACF,OAAO,SAAS,YAChB,MAAM,QAAQ,KAAK,SAAS,KAC3B,EAAe,UAAU,aAAa;AAE3C;AA0BO,SAAS,uBACd,OACsB;AACtB,MAAI,CAAC,MAAM,QAAQ,KAAK,EAAG,QAAO,CAAC;AACnC,QAAM,MAA4B,CAAC;AACnC,aAAW,QAAQ,OAAO;AACxB,QAAI,CAAC,WAAW,IAAI,EAAG;AACvB,eAAW,OAAO,KAAK,KAAK,WAAW;AAMrC,UAAI,iBAAiB,GAAG,EAAG,KAAI,KAAK,GAAG;AAAA,IACzC;AAAA,EACF;AACA,SAAO;AACT;AAmBA,MAAM,uBAAuB;AAAA,EAC3B;AAAA,EACA;AAAA,EACA;AAAA,EACA;AACF;AAOA,SAAS,iBAAiB,KAAyC;AACjE,MAAI,CAAC,OAAO,OAAO,QAAQ,YAAY,MAAM,QAAQ,GAAG,EAAG,QAAO;AAClE,SAAO,qBAAqB,KAAK,CAAC,QAAQ,OAAO,GAAG;AACtD;","names":[]}
package/lib/types.d.mts CHANGED
@@ -110,6 +110,16 @@ interface DeleteSurfaceEnvelope {
110
110
  interface ActionEnvelope {
111
111
  action: A2uiClientAction;
112
112
  }
113
+ /**
114
+ * The server → client A2UI envelopes: the surfaces an agent renders. These are
115
+ * what a renderer consumes, so {@link a2uiEnvelopesFromParts} yields exactly
116
+ * this type (an {@link ActionEnvelope} never rides a render stream).
117
+ *
118
+ * This union is structurally compatible with the message unions of the renderer
119
+ * packages (e.g. `@a2ui/web_core`'s `A2uiMessage`), so envelopes read off a
120
+ * chunk can be handed straight to a renderer without a cast.
121
+ */
122
+ type A2uiServerEnvelope = CreateSurfaceEnvelope | UpdateComponentsEnvelope | UpdateDataModelEnvelope | DeleteSurfaceEnvelope;
113
123
  /**
114
124
  * A single A2UI envelope message.
115
125
  *
@@ -118,7 +128,7 @@ interface ActionEnvelope {
118
128
  * back as the next turn). Keeping both directions in one union lets
119
129
  * {@link a2uiPart} be the single constructor for every a2ui part.
120
130
  */
121
- type A2uiEnvelope = CreateSurfaceEnvelope | UpdateComponentsEnvelope | UpdateDataModelEnvelope | DeleteSurfaceEnvelope | ActionEnvelope;
131
+ type A2uiEnvelope = A2uiServerEnvelope | ActionEnvelope;
122
132
  /**
123
133
  * The canonical "a2ui part": a Genkit `data` part whose `data` is an object
124
134
  * `{ envelopes }` wrapping the array of A2UI envelopes, and whose
@@ -150,4 +160,4 @@ interface A2uiClientAction {
150
160
  context: Record<string, unknown>;
151
161
  }
152
162
 
153
- export { A2UI_MIME_TYPE, A2UI_VERSION, type A2uiClientAction, type A2uiComponent, type A2uiEnvelope, type A2uiPart, type ActionEnvelope, BASIC_CATALOG_ID, type CreateSurfaceEnvelope, type DeleteSurfaceEnvelope, SUPPORTED_VERSIONS, type SupportedVersion, type UpdateComponentsEnvelope, type UpdateDataModelEnvelope };
163
+ export { A2UI_MIME_TYPE, A2UI_VERSION, type A2uiClientAction, type A2uiComponent, type A2uiEnvelope, type A2uiPart, type A2uiServerEnvelope, type ActionEnvelope, BASIC_CATALOG_ID, type CreateSurfaceEnvelope, type DeleteSurfaceEnvelope, SUPPORTED_VERSIONS, type SupportedVersion, type UpdateComponentsEnvelope, type UpdateDataModelEnvelope };
package/lib/types.d.ts CHANGED
@@ -110,6 +110,16 @@ interface DeleteSurfaceEnvelope {
110
110
  interface ActionEnvelope {
111
111
  action: A2uiClientAction;
112
112
  }
113
+ /**
114
+ * The server → client A2UI envelopes: the surfaces an agent renders. These are
115
+ * what a renderer consumes, so {@link a2uiEnvelopesFromParts} yields exactly
116
+ * this type (an {@link ActionEnvelope} never rides a render stream).
117
+ *
118
+ * This union is structurally compatible with the message unions of the renderer
119
+ * packages (e.g. `@a2ui/web_core`'s `A2uiMessage`), so envelopes read off a
120
+ * chunk can be handed straight to a renderer without a cast.
121
+ */
122
+ type A2uiServerEnvelope = CreateSurfaceEnvelope | UpdateComponentsEnvelope | UpdateDataModelEnvelope | DeleteSurfaceEnvelope;
113
123
  /**
114
124
  * A single A2UI envelope message.
115
125
  *
@@ -118,7 +128,7 @@ interface ActionEnvelope {
118
128
  * back as the next turn). Keeping both directions in one union lets
119
129
  * {@link a2uiPart} be the single constructor for every a2ui part.
120
130
  */
121
- type A2uiEnvelope = CreateSurfaceEnvelope | UpdateComponentsEnvelope | UpdateDataModelEnvelope | DeleteSurfaceEnvelope | ActionEnvelope;
131
+ type A2uiEnvelope = A2uiServerEnvelope | ActionEnvelope;
122
132
  /**
123
133
  * The canonical "a2ui part": a Genkit `data` part whose `data` is an object
124
134
  * `{ envelopes }` wrapping the array of A2UI envelopes, and whose
@@ -150,4 +160,4 @@ interface A2uiClientAction {
150
160
  context: Record<string, unknown>;
151
161
  }
152
162
 
153
- export { A2UI_MIME_TYPE, A2UI_VERSION, type A2uiClientAction, type A2uiComponent, type A2uiEnvelope, type A2uiPart, type ActionEnvelope, BASIC_CATALOG_ID, type CreateSurfaceEnvelope, type DeleteSurfaceEnvelope, SUPPORTED_VERSIONS, type SupportedVersion, type UpdateComponentsEnvelope, type UpdateDataModelEnvelope };
163
+ export { A2UI_MIME_TYPE, A2UI_VERSION, type A2uiClientAction, type A2uiComponent, type A2uiEnvelope, type A2uiPart, type A2uiServerEnvelope, type ActionEnvelope, BASIC_CATALOG_ID, type CreateSurfaceEnvelope, type DeleteSurfaceEnvelope, SUPPORTED_VERSIONS, type SupportedVersion, type UpdateComponentsEnvelope, type UpdateDataModelEnvelope };
package/lib/types.js.map CHANGED
@@ -1 +1 @@
1
- {"version":3,"sources":["../src/types.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 * Shared A2UI protocol types and constants.\n *\n * This module is intentionally free of any Node-only dependencies so it can be\n * imported both on the server (the {@link a2ui} middleware) and in the browser\n * (the client transport helpers). It mirrors the wire shapes of the A2UI v0.9\n * specification without depending on `@a2ui/web_core` (the renderer packages are\n * only needed on the client).\n *\n * @module\n */\n\n/**\n * The MIME type that identifies an A2UI payload. This is stamped onto the\n * `metadata.mimeType` of the Genkit `data` part that carries A2UI envelopes,\n * matching the A2A binding of the A2UI spec exactly.\n */\nexport const A2UI_MIME_TYPE = 'application/a2ui+json';\n\n/** The default A2UI protocol version stamped on emitted envelopes. */\nexport const A2UI_VERSION = 'v0.9';\n\n/**\n * The catalog id of the A2UI \"Basic Catalog\" (v0.9). Surfaces created with the\n * basic catalog reference this id, and the client renderer registers a catalog\n * under the same id.\n */\nexport const BASIC_CATALOG_ID =\n 'https://a2ui.org/specification/v0_9/catalogs/basic/catalog.json';\n\n/**\n * The protocol versions this plugin knows how to emit. This is the single\n * source of truth: {@link SupportedVersion} is derived from it, and the\n * middleware's `version` config validates against it (so a typo like `'v0.10'`\n * is rejected at config time rather than producing envelopes the renderer\n * rejects at runtime).\n */\nexport const SUPPORTED_VERSIONS = ['v0.9', 'v0.9.1'] as const;\n\nexport type SupportedVersion = (typeof SUPPORTED_VERSIONS)[number];\n\n/**\n * A single component entry in an A2UI adjacency list. UI is expressed as a flat\n * list of components; the tree is reconstructed via `id` references. Exactly one\n * component has `id: \"root\"`.\n *\n * Beyond `component`/`id`/`weight`, every component carries catalog-specific\n * props (e.g. `text`, `children`, `child`, `action`), so this is intentionally\n * open-ended.\n */\nexport interface A2uiComponent {\n /** The catalog component type name, e.g. `Text`, `Column`, `Button`. */\n component: string;\n /** The unique id of this component. Exactly one component must be `\"root\"`. */\n id?: string;\n /** Relative flex weight when nested directly in a Row/Column. */\n weight?: number;\n /** Catalog-specific component props. */\n [prop: string]: unknown;\n}\n\n/** Creates (or re-initializes) a UI surface. */\nexport interface CreateSurfaceEnvelope {\n version: SupportedVersion;\n createSurface: {\n surfaceId: string;\n catalogId: string;\n theme?: unknown;\n sendDataModel?: boolean;\n };\n}\n\n/** Adds or replaces components on an existing surface. */\nexport interface UpdateComponentsEnvelope {\n version: SupportedVersion;\n updateComponents: {\n surfaceId: string;\n components: A2uiComponent[];\n };\n}\n\n/** Mutates a value in a surface's per-surface data model. */\nexport interface UpdateDataModelEnvelope {\n version: SupportedVersion;\n updateDataModel: {\n surfaceId: string;\n /** JSON-Pointer path. When omitted, `value` replaces the whole model. */\n path?: string;\n value?: unknown;\n };\n}\n\n/** Removes a surface. */\nexport interface DeleteSurfaceEnvelope {\n version: SupportedVersion;\n deleteSurface: {\n surfaceId: string;\n };\n}\n\n/**\n * A user action reported by a rendered surface (client → server), wrapped as an\n * envelope so it rides the same a2ui part channel as server → client envelopes.\n * This is the typed shape {@link actionToMessage} produces and\n * {@link A2uiEnvelope} recognizes on the way back in.\n */\nexport interface ActionEnvelope {\n action: A2uiClientAction;\n}\n\n/**\n * A single A2UI envelope message.\n *\n * Most variants flow server → client (surfaces the agent renders); the\n * {@link ActionEnvelope} variant flows client → server (a user interaction sent\n * back as the next turn). Keeping both directions in one union lets\n * {@link a2uiPart} be the single constructor for every a2ui part.\n */\nexport type A2uiEnvelope =\n | CreateSurfaceEnvelope\n | UpdateComponentsEnvelope\n | UpdateDataModelEnvelope\n | DeleteSurfaceEnvelope\n | ActionEnvelope;\n\n/**\n * The canonical \"a2ui part\": a Genkit `data` part whose `data` is an object\n * `{ envelopes }` wrapping the array of A2UI envelopes, and whose\n * `metadata.mimeType` is {@link A2UI_MIME_TYPE}.\n *\n * The array is wrapped in an object (rather than being the `data` value\n * directly) so the payload is a `Map<String, dynamic>`-shaped object on every\n * runtime — some (e.g. Dart) expect a data part's `data` to be an object, not a\n * bare array.\n */\nexport interface A2uiPart {\n data: { envelopes: A2uiEnvelope[] };\n metadata: {\n mimeType: typeof A2UI_MIME_TYPE;\n [key: string]: unknown;\n };\n}\n\n/**\n * A client → server user action reported by a rendered surface (e.g. a button\n * press). Sent back to the agent as the next turn's input.\n */\nexport interface A2uiClientAction {\n name: string;\n surfaceId: string;\n sourceComponentId: string;\n timestamp: string;\n context: Record<string, unknown>;\n}\n"],"mappings":";;;;;;;;;;;;;;;;;;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAiCO,MAAM,iBAAiB;AAGvB,MAAM,eAAe;AAOrB,MAAM,mBACX;AASK,MAAM,qBAAqB,CAAC,QAAQ,QAAQ;","names":[]}
1
+ {"version":3,"sources":["../src/types.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 * Shared A2UI protocol types and constants.\n *\n * This module is intentionally free of any Node-only dependencies so it can be\n * imported both on the server (the {@link a2ui} middleware) and in the browser\n * (the client transport helpers). It mirrors the wire shapes of the A2UI v0.9\n * specification without depending on `@a2ui/web_core` (the renderer packages are\n * only needed on the client).\n *\n * @module\n */\n\n/**\n * The MIME type that identifies an A2UI payload. This is stamped onto the\n * `metadata.mimeType` of the Genkit `data` part that carries A2UI envelopes,\n * matching the A2A binding of the A2UI spec exactly.\n */\nexport const A2UI_MIME_TYPE = 'application/a2ui+json';\n\n/** The default A2UI protocol version stamped on emitted envelopes. */\nexport const A2UI_VERSION = 'v0.9';\n\n/**\n * The catalog id of the A2UI \"Basic Catalog\" (v0.9). Surfaces created with the\n * basic catalog reference this id, and the client renderer registers a catalog\n * under the same id.\n */\nexport const BASIC_CATALOG_ID =\n 'https://a2ui.org/specification/v0_9/catalogs/basic/catalog.json';\n\n/**\n * The protocol versions this plugin knows how to emit. This is the single\n * source of truth: {@link SupportedVersion} is derived from it, and the\n * middleware's `version` config validates against it (so a typo like `'v0.10'`\n * is rejected at config time rather than producing envelopes the renderer\n * rejects at runtime).\n */\nexport const SUPPORTED_VERSIONS = ['v0.9', 'v0.9.1'] as const;\n\nexport type SupportedVersion = (typeof SUPPORTED_VERSIONS)[number];\n\n/**\n * A single component entry in an A2UI adjacency list. UI is expressed as a flat\n * list of components; the tree is reconstructed via `id` references. Exactly one\n * component has `id: \"root\"`.\n *\n * Beyond `component`/`id`/`weight`, every component carries catalog-specific\n * props (e.g. `text`, `children`, `child`, `action`), so this is intentionally\n * open-ended.\n */\nexport interface A2uiComponent {\n /** The catalog component type name, e.g. `Text`, `Column`, `Button`. */\n component: string;\n /** The unique id of this component. Exactly one component must be `\"root\"`. */\n id?: string;\n /** Relative flex weight when nested directly in a Row/Column. */\n weight?: number;\n /** Catalog-specific component props. */\n [prop: string]: unknown;\n}\n\n/** Creates (or re-initializes) a UI surface. */\nexport interface CreateSurfaceEnvelope {\n version: SupportedVersion;\n createSurface: {\n surfaceId: string;\n catalogId: string;\n theme?: unknown;\n sendDataModel?: boolean;\n };\n}\n\n/** Adds or replaces components on an existing surface. */\nexport interface UpdateComponentsEnvelope {\n version: SupportedVersion;\n updateComponents: {\n surfaceId: string;\n components: A2uiComponent[];\n };\n}\n\n/** Mutates a value in a surface's per-surface data model. */\nexport interface UpdateDataModelEnvelope {\n version: SupportedVersion;\n updateDataModel: {\n surfaceId: string;\n /** JSON-Pointer path. When omitted, `value` replaces the whole model. */\n path?: string;\n value?: unknown;\n };\n}\n\n/** Removes a surface. */\nexport interface DeleteSurfaceEnvelope {\n version: SupportedVersion;\n deleteSurface: {\n surfaceId: string;\n };\n}\n\n/**\n * A user action reported by a rendered surface (client → server), wrapped as an\n * envelope so it rides the same a2ui part channel as server → client envelopes.\n * This is the typed shape {@link actionToMessage} produces and\n * {@link A2uiEnvelope} recognizes on the way back in.\n */\nexport interface ActionEnvelope {\n action: A2uiClientAction;\n}\n\n/**\n * The server → client A2UI envelopes: the surfaces an agent renders. These are\n * what a renderer consumes, so {@link a2uiEnvelopesFromParts} yields exactly\n * this type (an {@link ActionEnvelope} never rides a render stream).\n *\n * This union is structurally compatible with the message unions of the renderer\n * packages (e.g. `@a2ui/web_core`'s `A2uiMessage`), so envelopes read off a\n * chunk can be handed straight to a renderer without a cast.\n */\nexport type A2uiServerEnvelope =\n | CreateSurfaceEnvelope\n | UpdateComponentsEnvelope\n | UpdateDataModelEnvelope\n | DeleteSurfaceEnvelope;\n\n/**\n * A single A2UI envelope message.\n *\n * Most variants flow server → client (surfaces the agent renders); the\n * {@link ActionEnvelope} variant flows client → server (a user interaction sent\n * back as the next turn). Keeping both directions in one union lets\n * {@link a2uiPart} be the single constructor for every a2ui part.\n */\nexport type A2uiEnvelope = A2uiServerEnvelope | ActionEnvelope;\n\n/**\n * The canonical \"a2ui part\": a Genkit `data` part whose `data` is an object\n * `{ envelopes }` wrapping the array of A2UI envelopes, and whose\n * `metadata.mimeType` is {@link A2UI_MIME_TYPE}.\n *\n * The array is wrapped in an object (rather than being the `data` value\n * directly) so the payload is a `Map<String, dynamic>`-shaped object on every\n * runtime — some (e.g. Dart) expect a data part's `data` to be an object, not a\n * bare array.\n */\nexport interface A2uiPart {\n data: { envelopes: A2uiEnvelope[] };\n metadata: {\n mimeType: typeof A2UI_MIME_TYPE;\n [key: string]: unknown;\n };\n}\n\n/**\n * A client → server user action reported by a rendered surface (e.g. a button\n * press). Sent back to the agent as the next turn's input.\n */\nexport interface A2uiClientAction {\n name: string;\n surfaceId: string;\n sourceComponentId: string;\n timestamp: string;\n context: Record<string, unknown>;\n}\n"],"mappings":";;;;;;;;;;;;;;;;;;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAiCO,MAAM,iBAAiB;AAGvB,MAAM,eAAe;AAOrB,MAAM,mBACX;AASK,MAAM,qBAAqB,CAAC,QAAQ,QAAQ;","names":[]}
package/lib/types.mjs.map CHANGED
@@ -1 +1 @@
1
- {"version":3,"sources":["../src/types.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 * Shared A2UI protocol types and constants.\n *\n * This module is intentionally free of any Node-only dependencies so it can be\n * imported both on the server (the {@link a2ui} middleware) and in the browser\n * (the client transport helpers). It mirrors the wire shapes of the A2UI v0.9\n * specification without depending on `@a2ui/web_core` (the renderer packages are\n * only needed on the client).\n *\n * @module\n */\n\n/**\n * The MIME type that identifies an A2UI payload. This is stamped onto the\n * `metadata.mimeType` of the Genkit `data` part that carries A2UI envelopes,\n * matching the A2A binding of the A2UI spec exactly.\n */\nexport const A2UI_MIME_TYPE = 'application/a2ui+json';\n\n/** The default A2UI protocol version stamped on emitted envelopes. */\nexport const A2UI_VERSION = 'v0.9';\n\n/**\n * The catalog id of the A2UI \"Basic Catalog\" (v0.9). Surfaces created with the\n * basic catalog reference this id, and the client renderer registers a catalog\n * under the same id.\n */\nexport const BASIC_CATALOG_ID =\n 'https://a2ui.org/specification/v0_9/catalogs/basic/catalog.json';\n\n/**\n * The protocol versions this plugin knows how to emit. This is the single\n * source of truth: {@link SupportedVersion} is derived from it, and the\n * middleware's `version` config validates against it (so a typo like `'v0.10'`\n * is rejected at config time rather than producing envelopes the renderer\n * rejects at runtime).\n */\nexport const SUPPORTED_VERSIONS = ['v0.9', 'v0.9.1'] as const;\n\nexport type SupportedVersion = (typeof SUPPORTED_VERSIONS)[number];\n\n/**\n * A single component entry in an A2UI adjacency list. UI is expressed as a flat\n * list of components; the tree is reconstructed via `id` references. Exactly one\n * component has `id: \"root\"`.\n *\n * Beyond `component`/`id`/`weight`, every component carries catalog-specific\n * props (e.g. `text`, `children`, `child`, `action`), so this is intentionally\n * open-ended.\n */\nexport interface A2uiComponent {\n /** The catalog component type name, e.g. `Text`, `Column`, `Button`. */\n component: string;\n /** The unique id of this component. Exactly one component must be `\"root\"`. */\n id?: string;\n /** Relative flex weight when nested directly in a Row/Column. */\n weight?: number;\n /** Catalog-specific component props. */\n [prop: string]: unknown;\n}\n\n/** Creates (or re-initializes) a UI surface. */\nexport interface CreateSurfaceEnvelope {\n version: SupportedVersion;\n createSurface: {\n surfaceId: string;\n catalogId: string;\n theme?: unknown;\n sendDataModel?: boolean;\n };\n}\n\n/** Adds or replaces components on an existing surface. */\nexport interface UpdateComponentsEnvelope {\n version: SupportedVersion;\n updateComponents: {\n surfaceId: string;\n components: A2uiComponent[];\n };\n}\n\n/** Mutates a value in a surface's per-surface data model. */\nexport interface UpdateDataModelEnvelope {\n version: SupportedVersion;\n updateDataModel: {\n surfaceId: string;\n /** JSON-Pointer path. When omitted, `value` replaces the whole model. */\n path?: string;\n value?: unknown;\n };\n}\n\n/** Removes a surface. */\nexport interface DeleteSurfaceEnvelope {\n version: SupportedVersion;\n deleteSurface: {\n surfaceId: string;\n };\n}\n\n/**\n * A user action reported by a rendered surface (client → server), wrapped as an\n * envelope so it rides the same a2ui part channel as server → client envelopes.\n * This is the typed shape {@link actionToMessage} produces and\n * {@link A2uiEnvelope} recognizes on the way back in.\n */\nexport interface ActionEnvelope {\n action: A2uiClientAction;\n}\n\n/**\n * A single A2UI envelope message.\n *\n * Most variants flow server → client (surfaces the agent renders); the\n * {@link ActionEnvelope} variant flows client → server (a user interaction sent\n * back as the next turn). Keeping both directions in one union lets\n * {@link a2uiPart} be the single constructor for every a2ui part.\n */\nexport type A2uiEnvelope =\n | CreateSurfaceEnvelope\n | UpdateComponentsEnvelope\n | UpdateDataModelEnvelope\n | DeleteSurfaceEnvelope\n | ActionEnvelope;\n\n/**\n * The canonical \"a2ui part\": a Genkit `data` part whose `data` is an object\n * `{ envelopes }` wrapping the array of A2UI envelopes, and whose\n * `metadata.mimeType` is {@link A2UI_MIME_TYPE}.\n *\n * The array is wrapped in an object (rather than being the `data` value\n * directly) so the payload is a `Map<String, dynamic>`-shaped object on every\n * runtime — some (e.g. Dart) expect a data part's `data` to be an object, not a\n * bare array.\n */\nexport interface A2uiPart {\n data: { envelopes: A2uiEnvelope[] };\n metadata: {\n mimeType: typeof A2UI_MIME_TYPE;\n [key: string]: unknown;\n };\n}\n\n/**\n * A client → server user action reported by a rendered surface (e.g. a button\n * press). Sent back to the agent as the next turn's input.\n */\nexport interface A2uiClientAction {\n name: string;\n surfaceId: string;\n sourceComponentId: string;\n timestamp: string;\n context: Record<string, unknown>;\n}\n"],"mappings":"AAiCO,MAAM,iBAAiB;AAGvB,MAAM,eAAe;AAOrB,MAAM,mBACX;AASK,MAAM,qBAAqB,CAAC,QAAQ,QAAQ;","names":[]}
1
+ {"version":3,"sources":["../src/types.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 * Shared A2UI protocol types and constants.\n *\n * This module is intentionally free of any Node-only dependencies so it can be\n * imported both on the server (the {@link a2ui} middleware) and in the browser\n * (the client transport helpers). It mirrors the wire shapes of the A2UI v0.9\n * specification without depending on `@a2ui/web_core` (the renderer packages are\n * only needed on the client).\n *\n * @module\n */\n\n/**\n * The MIME type that identifies an A2UI payload. This is stamped onto the\n * `metadata.mimeType` of the Genkit `data` part that carries A2UI envelopes,\n * matching the A2A binding of the A2UI spec exactly.\n */\nexport const A2UI_MIME_TYPE = 'application/a2ui+json';\n\n/** The default A2UI protocol version stamped on emitted envelopes. */\nexport const A2UI_VERSION = 'v0.9';\n\n/**\n * The catalog id of the A2UI \"Basic Catalog\" (v0.9). Surfaces created with the\n * basic catalog reference this id, and the client renderer registers a catalog\n * under the same id.\n */\nexport const BASIC_CATALOG_ID =\n 'https://a2ui.org/specification/v0_9/catalogs/basic/catalog.json';\n\n/**\n * The protocol versions this plugin knows how to emit. This is the single\n * source of truth: {@link SupportedVersion} is derived from it, and the\n * middleware's `version` config validates against it (so a typo like `'v0.10'`\n * is rejected at config time rather than producing envelopes the renderer\n * rejects at runtime).\n */\nexport const SUPPORTED_VERSIONS = ['v0.9', 'v0.9.1'] as const;\n\nexport type SupportedVersion = (typeof SUPPORTED_VERSIONS)[number];\n\n/**\n * A single component entry in an A2UI adjacency list. UI is expressed as a flat\n * list of components; the tree is reconstructed via `id` references. Exactly one\n * component has `id: \"root\"`.\n *\n * Beyond `component`/`id`/`weight`, every component carries catalog-specific\n * props (e.g. `text`, `children`, `child`, `action`), so this is intentionally\n * open-ended.\n */\nexport interface A2uiComponent {\n /** The catalog component type name, e.g. `Text`, `Column`, `Button`. */\n component: string;\n /** The unique id of this component. Exactly one component must be `\"root\"`. */\n id?: string;\n /** Relative flex weight when nested directly in a Row/Column. */\n weight?: number;\n /** Catalog-specific component props. */\n [prop: string]: unknown;\n}\n\n/** Creates (or re-initializes) a UI surface. */\nexport interface CreateSurfaceEnvelope {\n version: SupportedVersion;\n createSurface: {\n surfaceId: string;\n catalogId: string;\n theme?: unknown;\n sendDataModel?: boolean;\n };\n}\n\n/** Adds or replaces components on an existing surface. */\nexport interface UpdateComponentsEnvelope {\n version: SupportedVersion;\n updateComponents: {\n surfaceId: string;\n components: A2uiComponent[];\n };\n}\n\n/** Mutates a value in a surface's per-surface data model. */\nexport interface UpdateDataModelEnvelope {\n version: SupportedVersion;\n updateDataModel: {\n surfaceId: string;\n /** JSON-Pointer path. When omitted, `value` replaces the whole model. */\n path?: string;\n value?: unknown;\n };\n}\n\n/** Removes a surface. */\nexport interface DeleteSurfaceEnvelope {\n version: SupportedVersion;\n deleteSurface: {\n surfaceId: string;\n };\n}\n\n/**\n * A user action reported by a rendered surface (client → server), wrapped as an\n * envelope so it rides the same a2ui part channel as server → client envelopes.\n * This is the typed shape {@link actionToMessage} produces and\n * {@link A2uiEnvelope} recognizes on the way back in.\n */\nexport interface ActionEnvelope {\n action: A2uiClientAction;\n}\n\n/**\n * The server → client A2UI envelopes: the surfaces an agent renders. These are\n * what a renderer consumes, so {@link a2uiEnvelopesFromParts} yields exactly\n * this type (an {@link ActionEnvelope} never rides a render stream).\n *\n * This union is structurally compatible with the message unions of the renderer\n * packages (e.g. `@a2ui/web_core`'s `A2uiMessage`), so envelopes read off a\n * chunk can be handed straight to a renderer without a cast.\n */\nexport type A2uiServerEnvelope =\n | CreateSurfaceEnvelope\n | UpdateComponentsEnvelope\n | UpdateDataModelEnvelope\n | DeleteSurfaceEnvelope;\n\n/**\n * A single A2UI envelope message.\n *\n * Most variants flow server → client (surfaces the agent renders); the\n * {@link ActionEnvelope} variant flows client → server (a user interaction sent\n * back as the next turn). Keeping both directions in one union lets\n * {@link a2uiPart} be the single constructor for every a2ui part.\n */\nexport type A2uiEnvelope = A2uiServerEnvelope | ActionEnvelope;\n\n/**\n * The canonical \"a2ui part\": a Genkit `data` part whose `data` is an object\n * `{ envelopes }` wrapping the array of A2UI envelopes, and whose\n * `metadata.mimeType` is {@link A2UI_MIME_TYPE}.\n *\n * The array is wrapped in an object (rather than being the `data` value\n * directly) so the payload is a `Map<String, dynamic>`-shaped object on every\n * runtime — some (e.g. Dart) expect a data part's `data` to be an object, not a\n * bare array.\n */\nexport interface A2uiPart {\n data: { envelopes: A2uiEnvelope[] };\n metadata: {\n mimeType: typeof A2UI_MIME_TYPE;\n [key: string]: unknown;\n };\n}\n\n/**\n * A client → server user action reported by a rendered surface (e.g. a button\n * press). Sent back to the agent as the next turn's input.\n */\nexport interface A2uiClientAction {\n name: string;\n surfaceId: string;\n sourceComponentId: string;\n timestamp: string;\n context: Record<string, unknown>;\n}\n"],"mappings":"AAiCO,MAAM,iBAAiB;AAGvB,MAAM,eAAe;AAOrB,MAAM,mBACX;AASK,MAAM,qBAAqB,CAAC,QAAQ,QAAQ;","names":[]}
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@genkit-ai/a2ui",
3
- "version": "0.5.0-rc.0",
3
+ "version": "0.5.0-rc.2",
4
4
  "description": "A Genkit plugin providing A2UI (Agent-to-UI) streaming UI protocol support via model middleware.",
5
5
  "keywords": [
6
6
  "genkit",
@@ -55,7 +55,7 @@
55
55
  "LICENSE"
56
56
  ],
57
57
  "peerDependencies": {
58
- "genkit": "^1.42.0-rc.0"
58
+ "genkit": "^1.42.0-rc.2"
59
59
  },
60
60
  "devDependencies": {
61
61
  "@types/node": "^20.11.16",
@@ -64,7 +64,7 @@
64
64
  "tsup": "^8.3.5",
65
65
  "tsx": "^4.19.2",
66
66
  "typescript": "^5.9.3",
67
- "genkit": "1.42.0-rc.0"
67
+ "genkit": "1.42.0-rc.2"
68
68
  },
69
69
  "engines": {
70
70
  "node": ">=20"