diff --git a/apps/website/content/docs/render/a2ui/catalog.mdx b/apps/website/content/docs/render/a2ui/catalog.mdx
index 4110af97c..cc1213e53 100644
--- a/apps/website/content/docs/render/a2ui/catalog.mdx
+++ b/apps/website/content/docs/render/a2ui/catalog.mdx
@@ -159,10 +159,10 @@ Renders a button that dispatches an action when clicked.
**Action types:**
```json
-// Emit a named event (sent back to the agent)
-{"action": {"event": {"name": "submit", "context": {"formId": "contact"}}}}
+// Emit a named event with resolved context (sent back to the agent as v0.9 action)
+{"action": {"event": {"name": "submit", "context": {"email": {"path": "/email"}, "formId": "contact"}}}}
-// Execute a local function (e.g., open a URL)
+// Execute a local function (e.g., open a URL) — agent never sees this
{"action": {"functionCall": {"call": "openUrl", "args": {"url": "https://example.com"}}}}
```
diff --git a/apps/website/content/docs/render/a2ui/overview.mdx b/apps/website/content/docs/render/a2ui/overview.mdx
index a1b35dd86..9aa7b0ace 100644
--- a/apps/website/content/docs/render/a2ui/overview.mdx
+++ b/apps/website/content/docs/render/a2ui/overview.mdx
@@ -289,6 +289,111 @@ Validation styling uses CSS custom properties:
| `--a2ui-input-bg` | `rgba(255,255,255,0.05)` | Input background |
| `--a2ui-label` | `rgba(255,255,255,0.6)` | Label text color |
+## Events & Data Model Transport
+
+When a user triggers an event action (e.g., clicking a button with `action.event`), the Angular renderer builds a v0.9-compliant action message and sends it back to the agent. Local actions (`action.functionCall`) execute client-side only — the agent never sees them.
+
+### Action Message Shape
+
+The outbound message follows the [v0.9 spec](https://a2ui.org):
+
+```json
+{
+ "version": "v0.9",
+ "action": {
+ "name": "formSubmit",
+ "surfaceId": "contact",
+ "sourceComponentId": "submit-btn",
+ "timestamp": "2026-04-10T14:30:00.000Z",
+ "context": {
+ "name": "Alice",
+ "email": "alice@example.com"
+ }
+ }
+}
+```
+
+| Field | Description |
+|-------|-------------|
+| `version` | Always `"v0.9"` |
+| `action.name` | The event name from the component's `action.event.name` |
+| `action.surfaceId` | The surface that owns this component |
+| `action.sourceComponentId` | The `id` of the component that triggered the event |
+| `action.timestamp` | ISO 8601 timestamp of when the action was dispatched |
+| `action.context` | Resolved values from `action.event.context` — path refs and function calls are evaluated against the current data model |
+
+### Context Resolution
+
+Context values in `action.event.context` are `DynamicValue`s — they can be literals, path references, or function calls. They are resolved at dispatch time against the current data model:
+
+```json
+{
+ "action": {
+ "event": {
+ "name": "formSubmit",
+ "context": {
+ "name": {"path": "/name"},
+ "email": {"path": "/email"},
+ "total": {"call": "formatCurrency", "args": {"value": {"path": "/amount"}}}
+ }
+ }
+ }
+}
+```
+
+When the user clicks the button, the renderer resolves `/name` and `/email` from the data model and calls `formatCurrency` on `/amount`, producing a flat `context` object with concrete values.
+
+### sendDataModel
+
+Set `sendDataModel: true` on `createSurface` to attach the full data model snapshot to every outbound action:
+
+```json
+{"type": "createSurface", "surfaceId": "contact", "catalogId": "basic", "sendDataModel": true}
+```
+
+When enabled, the action message includes a `metadata` field:
+
+```json
+{
+ "version": "v0.9",
+ "action": { "..." : "..." },
+ "metadata": {
+ "a2uiClientDataModel": {
+ "version": "v0.9",
+ "surfaces": {
+ "contact": {
+ "name": "Alice",
+ "email": "alice@example.com",
+ "department": "Engineering"
+ }
+ }
+ }
+ }
+}
+```
+
+The data model is only sent with event actions — there are no passive change notifications on input changes. This matches the v0.9 spec requirement that the data model piggybacks on outbound messages.
+
+### Angular Integration
+
+`A2uiSurfaceComponent` exposes two outputs:
+
+| Output | Type | Description |
+|--------|------|-------------|
+| `(action)` | `A2uiActionMessage` | Agent-bound action messages — the complete v0.9 envelope |
+| `(events)` | `RenderEvent` | All render events (state changes, handler calls, lifecycle) for observation |
+
+`ChatComponent` auto-routes `(action)` events to the agent as human messages. For standalone usage, bind `(action)` directly:
+
+```html
+
+```
+
## What's Next
diff --git a/cockpit/chat/a2ui/python/src/graph.py b/cockpit/chat/a2ui/python/src/graph.py
index f66894a8e..07f1e5613 100644
--- a/cockpit/chat/a2ui/python/src/graph.py
+++ b/cockpit/chat/a2ui/python/src/graph.py
@@ -12,7 +12,7 @@
A2UI_PREFIX = "---a2ui_JSON---"
CONTACT_FORM_JSONL = A2UI_PREFIX + "\n" + "\n".join([
- json.dumps({"type": "createSurface", "surfaceId": "contact", "catalogId": "basic"}),
+ json.dumps({"type": "createSurface", "surfaceId": "contact", "catalogId": "basic", "sendDataModel": True}),
json.dumps({"type": "updateDataModel", "surfaceId": "contact", "value": {
"name": "", "email": "", "department": "Engineering", "consent": False,
}}),
@@ -56,7 +56,11 @@
]}},
"message": "Complete all required fields and agree to be contacted"},
],
- "action": {"event": {"name": "formSubmit", "context": {"formId": "contact"}}}},
+ "action": {"event": {"name": "formSubmit", "context": {
+ "name": {"path": "/name"},
+ "email": {"path": "/email"},
+ "department": {"path": "/department"},
+ }}}},
]}),
])
@@ -71,10 +75,10 @@ def build_a2ui_graph():
async def create_form(state: MessagesState) -> dict:
last = state["messages"][-1]
- # If this is an a2ui_event, route to event handling
+ # If this is a v0.9 action message, route to event handling
try:
payload = json.loads(last.content)
- if isinstance(payload, dict) and payload.get("type") == "a2ui_event":
+ if isinstance(payload, dict) and payload.get("version") == "v0.9" and "action" in payload:
return await handle_event(state, payload)
except (json.JSONDecodeError, AttributeError):
pass
@@ -83,9 +87,28 @@ async def create_form(state: MessagesState) -> dict:
return {"messages": [AIMessage(content=CONTACT_FORM_JSONL)]}
async def handle_event(state: MessagesState, payload: dict) -> dict:
- name = payload.get("context", {}).get("formId", "unknown")
+ action = payload["action"]
+ context = action.get("context", {})
+ name = context.get("name", "Unknown")
+ email = context.get("email", "not provided")
+ department = context.get("department", "not specified")
+
+ # Full data model is available via metadata when sendDataModel is true.
+ # Use it when you need values beyond what context provides.
+ data_model = ( # noqa: F841
+ payload.get("metadata", {})
+ .get("a2uiClientDataModel", {})
+ .get("surfaces", {})
+ .get(action["surfaceId"], {})
+ )
+
return {"messages": [AIMessage(
- content=f"Thanks for submitting the **{name}** form! We'll be in touch soon.",
+ content=(
+ f"Thanks **{name}**! We received your submission:\n\n"
+ f"- **Email:** {email}\n"
+ f"- **Department:** {department}\n\n"
+ f"We'll be in touch soon."
+ ),
)]}
graph = StateGraph(MessagesState)
diff --git a/libs/a2ui/src/index.ts b/libs/a2ui/src/index.ts
index b2c38fcfc..e0efc8fe0 100644
--- a/libs/a2ui/src/index.ts
+++ b/libs/a2ui/src/index.ts
@@ -7,6 +7,7 @@ export type {
A2uiComponent,
A2uiCreateSurface, A2uiUpdateComponents, A2uiUpdateDataModel, A2uiDeleteSurface,
A2uiMessage, A2uiSurface,
+ A2uiClientDataModel, A2uiActionMessage,
} from './lib/types';
export { getByPointer, setByPointer, deleteByPointer } from './lib/pointer';
export { createA2uiMessageParser } from './lib/parser';
diff --git a/libs/a2ui/src/lib/functions.ts b/libs/a2ui/src/lib/functions.ts
index 01d689ab1..37815e7d7 100644
--- a/libs/a2ui/src/lib/functions.ts
+++ b/libs/a2ui/src/lib/functions.ts
@@ -57,8 +57,10 @@ const FUNCTIONS: Record = {
// Navigation
openUrl: (args) => {
- if (typeof globalThis.window !== 'undefined') {
- globalThis.window.open(String(args['url']), '_blank');
+ // eslint-disable-next-line @typescript-eslint/no-explicit-any
+ const win = globalThis as any;
+ if (typeof win['window'] !== 'undefined') {
+ win['window'].open(String(args['url']), '_blank');
}
return null;
},
diff --git a/libs/a2ui/src/lib/types.ts b/libs/a2ui/src/lib/types.ts
index 225626546..cd2c694a0 100644
--- a/libs/a2ui/src/lib/types.ts
+++ b/libs/a2ui/src/lib/types.ts
@@ -106,6 +106,30 @@ export interface A2uiSurface {
surfaceId: string;
catalogId: string;
theme?: A2uiTheme;
+ sendDataModel?: boolean;
components: Map;
dataModel: Record;
}
+
+// --- v0.9 Outbound Action ---
+
+/** v0.9 client data model envelope — attached when sendDataModel is true. */
+export interface A2uiClientDataModel {
+ version: 'v0.9';
+ surfaces: Record>;
+}
+
+/** v0.9 outbound action message — sent when a component's event action fires. */
+export interface A2uiActionMessage {
+ version: 'v0.9';
+ action: {
+ name: string;
+ surfaceId: string;
+ sourceComponentId: string;
+ timestamp: string;
+ context: Record;
+ };
+ metadata?: {
+ a2uiClientDataModel: A2uiClientDataModel;
+ };
+}
diff --git a/libs/chat/src/lib/a2ui/surface-store.spec.ts b/libs/chat/src/lib/a2ui/surface-store.spec.ts
index eb21a8396..39015bbdb 100644
--- a/libs/chat/src/lib/a2ui/surface-store.spec.ts
+++ b/libs/chat/src/lib/a2ui/surface-store.spec.ts
@@ -96,4 +96,16 @@ describe('createA2uiSurfaceStore', () => {
store.apply({ type: 'updateComponents', surfaceId: 'nope', components: [] });
expect(store.surfaces().size).toBe(0);
});
+
+ it('preserves sendDataModel flag from createSurface', () => {
+ const store = setup();
+ store.apply({ type: 'createSurface', surfaceId: 's1', catalogId: 'basic', sendDataModel: true });
+ expect(store.surfaces().get('s1')!.sendDataModel).toBe(true);
+ });
+
+ it('defaults sendDataModel to undefined when not set', () => {
+ const store = setup();
+ store.apply({ type: 'createSurface', surfaceId: 's1', catalogId: 'basic' });
+ expect(store.surfaces().get('s1')!.sendDataModel).toBeUndefined();
+ });
});
diff --git a/libs/chat/src/lib/a2ui/surface-store.ts b/libs/chat/src/lib/a2ui/surface-store.ts
index 5492bc683..1b3972dbd 100644
--- a/libs/chat/src/lib/a2ui/surface-store.ts
+++ b/libs/chat/src/lib/a2ui/surface-store.ts
@@ -22,6 +22,7 @@ export function createA2uiSurfaceStore(): A2uiSurfaceStore {
surfaceId: message.surfaceId,
catalogId: message.catalogId,
theme: message.theme,
+ sendDataModel: message.sendDataModel,
components: new Map(),
dataModel: {},
});
diff --git a/libs/chat/src/lib/a2ui/surface.component.spec.ts b/libs/chat/src/lib/a2ui/surface.component.spec.ts
index 021aa045a..cbd565971 100644
--- a/libs/chat/src/lib/a2ui/surface.component.spec.ts
+++ b/libs/chat/src/lib/a2ui/surface.component.spec.ts
@@ -1,7 +1,7 @@
// SPDX-License-Identifier: PolyForm-Noncommercial-1.0.0
import { describe, it, expect } from 'vitest';
import type { A2uiSurface, A2uiComponent } from '@cacheplane/a2ui';
-import { surfaceToSpec } from './surface.component';
+import { surfaceToSpec, buildA2uiActionMessage } from './surface.component';
describe('A2uiSurfaceComponent — data flow', () => {
function makeSurface(components: A2uiComponent[], dataModel: Record = {}): A2uiSurface {
@@ -79,7 +79,7 @@ describe('surfaceToSpec — action mapping', () => {
expect(btnElement.on).toBeDefined();
expect(btnElement.on!['click']).toEqual({
action: 'a2ui:event',
- params: { surfaceId: 's1', name: 'formSubmit', context: { formId: 'signup' } },
+ params: { surfaceId: 's1', sourceComponentId: 'btn', name: 'formSubmit', context: { formId: 'signup' } },
});
expect(btnElement.props['action']).toBeUndefined();
});
@@ -154,6 +154,97 @@ describe('A2uiSurfaceComponent — consumer handlers', () => {
});
});
+describe('surfaceToSpec — v0.9 event action', () => {
+ function makeSurface(components: A2uiComponent[], dataModel: Record = {}): A2uiSurface {
+ const map = new Map();
+ for (const c of components) map.set(c.id, c);
+ return { surfaceId: 's1', catalogId: 'basic', components: map, dataModel };
+ }
+
+ it('resolves context DynamicValue paths against data model', () => {
+ const surface = makeSurface(
+ [
+ { id: 'root', component: 'Column', children: ['btn'] },
+ {
+ id: 'btn',
+ component: 'Button',
+ label: 'Submit',
+ action: { event: { name: 'formSubmit', context: { email: { path: '/email' } } } },
+ },
+ ],
+ { email: 'alice@example.com' },
+ );
+ const spec = surfaceToSpec(surface)!;
+ const params = spec.elements['btn'].on!['click'].params;
+ expect(params['context']).toEqual({ email: 'alice@example.com' });
+ });
+
+ it('resolves context FunctionCall values', () => {
+ const surface = makeSurface(
+ [
+ { id: 'root', component: 'Column', children: ['btn'] },
+ {
+ id: 'btn',
+ component: 'Button',
+ label: 'Format',
+ action: { event: { name: 'show', context: { price: { call: 'formatCurrency', args: { value: { path: '/amount' } } } } } },
+ },
+ ],
+ { amount: 42 },
+ );
+ const spec = surfaceToSpec(surface)!;
+ const params = spec.elements['btn'].on!['click'].params;
+ expect(params['context']).toEqual({ price: '$42.00' });
+ });
+
+ it('passes literal context values through unchanged', () => {
+ const surface = makeSurface(
+ [
+ { id: 'root', component: 'Column', children: ['btn'] },
+ {
+ id: 'btn',
+ component: 'Button',
+ label: 'Go',
+ action: { event: { name: 'navigate', context: { page: 'home' } } },
+ },
+ ],
+ );
+ const spec = surfaceToSpec(surface)!;
+ const params = spec.elements['btn'].on!['click'].params;
+ expect(params['context']).toEqual({ page: 'home' });
+ });
+
+ it('includes sourceComponentId in event action params', () => {
+ const surface = makeSurface([
+ { id: 'root', component: 'Column', children: ['submit-btn'] },
+ {
+ id: 'submit-btn',
+ component: 'Button',
+ label: 'Submit',
+ action: { event: { name: 'formSubmit' } },
+ },
+ ]);
+ const spec = surfaceToSpec(surface)!;
+ const params = spec.elements['submit-btn'].on!['click'].params;
+ expect(params['sourceComponentId']).toBe('submit-btn');
+ });
+
+ it('defaults context to empty object when not specified', () => {
+ const surface = makeSurface([
+ { id: 'root', component: 'Column', children: ['btn'] },
+ {
+ id: 'btn',
+ component: 'Button',
+ label: 'Click',
+ action: { event: { name: 'clicked' } },
+ },
+ ]);
+ const spec = surfaceToSpec(surface)!;
+ const params = spec.elements['btn'].on!['click'].params;
+ expect(params['context']).toEqual({});
+ });
+});
+
describe('surfaceToSpec — validation', () => {
function makeSurface(components: A2uiComponent[], dataModel: Record = {}): A2uiSurface {
const map = new Map();
@@ -246,3 +337,71 @@ describe('surfaceToSpec — validation', () => {
expect(spec.elements['root'].props['checks']).toBeUndefined();
});
});
+
+describe('buildA2uiActionMessage', () => {
+ function makeSurface(
+ components: A2uiComponent[],
+ dataModel: Record = {},
+ sendDataModel?: boolean,
+ ): A2uiSurface {
+ const map = new Map();
+ for (const c of components) map.set(c.id, c);
+ return { surfaceId: 's1', catalogId: 'basic', sendDataModel, components: map, dataModel };
+ }
+
+ it('builds a v0.9 action message with all required fields', () => {
+ const surface = makeSurface([{ id: 'root', component: 'Text' }]);
+ const params = {
+ surfaceId: 's1',
+ sourceComponentId: 'submit-btn',
+ name: 'formSubmit',
+ context: { email: 'alice@example.com' },
+ };
+ const msg = buildA2uiActionMessage(params, surface);
+ expect(msg.version).toBe('v0.9');
+ expect(msg.action.name).toBe('formSubmit');
+ expect(msg.action.surfaceId).toBe('s1');
+ expect(msg.action.sourceComponentId).toBe('submit-btn');
+ expect(msg.action.context).toEqual({ email: 'alice@example.com' });
+ expect(msg.action.timestamp).toMatch(/^\d{4}-\d{2}-\d{2}T/);
+ expect(msg.metadata).toBeUndefined();
+ });
+
+ it('attaches data model when sendDataModel is true', () => {
+ const surface = makeSurface(
+ [{ id: 'root', component: 'Text' }],
+ { name: 'Alice', email: 'alice@co.com' },
+ true,
+ );
+ const params = { surfaceId: 's1', sourceComponentId: 'btn', name: 'submit', context: {} };
+ const msg = buildA2uiActionMessage(params, surface);
+ expect(msg.metadata).toBeDefined();
+ expect(msg.metadata!.a2uiClientDataModel.version).toBe('v0.9');
+ expect(msg.metadata!.a2uiClientDataModel.surfaces['s1']).toEqual({ name: 'Alice', email: 'alice@co.com' });
+ });
+
+ it('does not attach data model when sendDataModel is false', () => {
+ const surface = makeSurface(
+ [{ id: 'root', component: 'Text' }],
+ { name: 'Alice' },
+ false,
+ );
+ const params = { surfaceId: 's1', sourceComponentId: 'btn', name: 'submit', context: {} };
+ const msg = buildA2uiActionMessage(params, surface);
+ expect(msg.metadata).toBeUndefined();
+ });
+
+ it('does not attach data model when sendDataModel is undefined', () => {
+ const surface = makeSurface([{ id: 'root', component: 'Text' }], { name: 'Alice' });
+ const params = { surfaceId: 's1', sourceComponentId: 'btn', name: 'submit', context: {} };
+ const msg = buildA2uiActionMessage(params, surface);
+ expect(msg.metadata).toBeUndefined();
+ });
+
+ it('defaults context to empty object when not provided in params', () => {
+ const surface = makeSurface([{ id: 'root', component: 'Text' }]);
+ const params = { surfaceId: 's1', sourceComponentId: 'btn', name: 'click' } as any;
+ const msg = buildA2uiActionMessage(params, surface);
+ expect(msg.action.context).toEqual({});
+ });
+});
diff --git a/libs/chat/src/lib/a2ui/surface.component.ts b/libs/chat/src/lib/a2ui/surface.component.ts
index 9091c606f..896b190e2 100644
--- a/libs/chat/src/lib/a2ui/surface.component.ts
+++ b/libs/chat/src/lib/a2ui/surface.component.ts
@@ -3,7 +3,7 @@ import {
Component, computed, input, output, ChangeDetectionStrategy,
} from '@angular/core';
import type { Spec } from '@json-render/core';
-import type { A2uiSurface, A2uiChildTemplate } from '@cacheplane/a2ui';
+import type { A2uiSurface, A2uiChildTemplate, A2uiActionMessage } from '@cacheplane/a2ui';
import { resolveDynamic, getByPointer, evaluateCheckRules } from '@cacheplane/a2ui';
import { RenderSpecComponent, toRenderRegistry } from '@cacheplane/render';
import type { ViewRegistry, RenderEvent } from '@cacheplane/render';
@@ -41,10 +41,21 @@ export function surfaceToSpec(surface: A2uiSurface): Spec | null {
if (comp.action) {
if ('event' in comp.action) {
const evt = comp.action.event;
+ const resolvedContext: Record = {};
+ if (evt.context) {
+ for (const [key, value] of Object.entries(evt.context)) {
+ resolvedContext[key] = resolveDynamic(value, surface.dataModel);
+ }
+ }
on = {
click: {
action: 'a2ui:event',
- params: { surfaceId: surface.surfaceId, name: evt.name, context: evt.context },
+ params: {
+ surfaceId: surface.surfaceId,
+ sourceComponentId: id,
+ name: evt.name,
+ context: resolvedContext,
+ },
},
};
} else if ('functionCall' in comp.action) {
@@ -102,6 +113,32 @@ export function surfaceToSpec(surface: A2uiSurface): Spec | null {
return { root: 'root', elements, state: surface.dataModel } as Spec;
}
+/** Builds a v0.9 A2uiActionMessage from handler params and the current surface. */
+export function buildA2uiActionMessage(
+ params: Record,
+ surface: A2uiSurface,
+): A2uiActionMessage {
+ const message: A2uiActionMessage = {
+ version: 'v0.9',
+ action: {
+ name: params['name'] as string,
+ surfaceId: surface.surfaceId,
+ sourceComponentId: params['sourceComponentId'] as string,
+ timestamp: new Date().toISOString(),
+ context: (params['context'] as Record) ?? {},
+ },
+ };
+ if (surface.sendDataModel) {
+ message.metadata = {
+ a2uiClientDataModel: {
+ version: 'v0.9',
+ surfaces: { [surface.surfaceId]: surface.dataModel },
+ },
+ };
+ }
+ return message;
+}
+
@Component({
selector: 'a2ui-surface',
standalone: true,
@@ -123,6 +160,7 @@ export class A2uiSurfaceComponent {
readonly catalog = input.required();
readonly handlers = input) => unknown | Promise>>({});
readonly events = output();
+ readonly action = output();
/** Convert the A2UI surface to a json-render Spec for rendering. */
readonly spec = computed(() => surfaceToSpec(this.surface()));
@@ -135,7 +173,9 @@ export class A2uiSurfaceComponent {
const consumerHandlers = this.handlers();
return {
'a2ui:event': (params: Record) => {
- return params;
+ const message = buildA2uiActionMessage(params, this.surface());
+ this.action.emit(message);
+ return message;
},
'a2ui:localAction': (params: Record) => {
const call = params['call'] as string;
diff --git a/libs/chat/src/lib/compositions/chat/chat.component.ts b/libs/chat/src/lib/compositions/chat/chat.component.ts
index c5ecd584a..671ab40a0 100644
--- a/libs/chat/src/lib/compositions/chat/chat.component.ts
+++ b/libs/chat/src/lib/compositions/chat/chat.component.ts
@@ -15,6 +15,7 @@ import {
import { DomSanitizer } from '@angular/platform-browser';
import type { AgentRef } from '@cacheplane/angular';
import type { ViewRegistry, RenderEvent } from '@cacheplane/render';
+import type { A2uiActionMessage } from '@cacheplane/a2ui';
import type { StateStore } from '@json-render/core';
import { ChatMessagesComponent } from '../../primitives/chat-messages/chat-messages.component';
import { MessageTemplateDirective } from '../../primitives/chat-messages/message-template.directive';
@@ -154,6 +155,7 @@ import { KeyValuePipe } from '@angular/common';
[surface]="entry.value"
[catalog]="catalog"
[handlers]="handlers()"
+ (action)="onA2uiAction($event)"
(events)="onA2uiEvent($event, index, entry.key)"
/>
}
@@ -292,24 +294,13 @@ export class ChatComponent {
this.renderEvent.emit({ messageIndex, event });
}
- onA2uiEvent(event: RenderEvent, messageIndex: number, surfaceId: string): void {
- // Auto-route A2UI event actions back to the agent
- if (event.type === 'handler' && event.action === 'a2ui:event') {
- const params = event.params as Record;
- this.ref().submit({
- messages: [{
- role: 'human',
- content: JSON.stringify({
- type: 'a2ui_event',
- surfaceId: params['surfaceId'],
- name: params['name'],
- context: params['context'],
- }),
- }],
- });
- }
+ onA2uiAction(message: A2uiActionMessage): void {
+ this.ref().submit({
+ messages: [{ role: 'human', content: JSON.stringify(message) }],
+ });
+ }
- // Still emit for consumer observation/logging
+ onA2uiEvent(event: RenderEvent, messageIndex: number, surfaceId: string): void {
this.renderEvent.emit({ messageIndex, surfaceId, event });
}
}
diff --git a/libs/chat/src/public-api.ts b/libs/chat/src/public-api.ts
index 0f513baef..471c4403c 100644
--- a/libs/chat/src/public-api.ts
+++ b/libs/chat/src/public-api.ts
@@ -68,6 +68,8 @@ export type { A2uiSurfaceStore } from './lib/a2ui/surface-store';
export { A2uiSurfaceComponent } from './lib/a2ui/surface.component';
export { a2uiBasicCatalog } from './lib/a2ui/catalog/index';
export { A2uiValidationErrorsComponent } from './lib/a2ui/catalog/validation-errors.component';
+export { buildA2uiActionMessage } from './lib/a2ui/surface.component';
+export type { A2uiActionMessage, A2uiClientDataModel } from '@cacheplane/a2ui';
// Test utilities
export { createMockAgentRef } from './lib/testing/mock-agent-ref';