Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
74 changes: 74 additions & 0 deletions apps/website/content/docs/a2ui/getting-started/introduction.mdx
Original file line number Diff line number Diff line change
@@ -0,0 +1,74 @@
# Introduction

`@ngaf/a2ui` is the protocol layer for A2UI messages. It gives the rest of the framework a shared TypeScript vocabulary for agent-built surfaces, streamed JSONL messages, dynamic values, and outbound action payloads.

It does not render Angular components. It does not register handler functions. It does not decide how an agent should respond to a button click. Those jobs sit in `@ngaf/chat` and `@ngaf/render`.

## What the package owns

The public entry point exports four groups of tools:

| Area | Exports |
|------|---------|
| Wire types | `A2uiMessage`, `A2uiComponent`, component prop interfaces, data-model update types, action message types |
| Stream parsing | `createA2uiMessageParser()` |
| Data access | `getByPointer()`, `setByPointer()`, `deleteByPointer()` |
| Dynamic values | `resolveDynamic()`, `A2uiScope`, literal/path guards |

Use this package when you are building an adapter, validating an agent stream, testing A2UI payloads, or integrating a custom renderer with the same protocol surface that `@ngaf/chat` uses.

## Message flow

The parser expects newline-delimited JSON. Each line is checked for one known envelope key:

```text
surfaceUpdate
dataModelUpdate
beginRendering
deleteSurface
```

When a line parses and has one of those envelope keys, it is returned as an `A2uiMessage`. Unknown envelopes are ignored. Malformed lines are skipped. Incomplete JSON waits in the internal buffer until a newline arrives.

```ts
import { createA2uiMessageParser } from '@ngaf/a2ui';

const parser = createA2uiMessageParser();

const messages = parser.push(
'{"beginRendering":{"surfaceId":"checkout","root":"root"}}\n',
);
```

That posture is intentional. Agent streams are partial by nature. The low-level parser favors safe continuation over throwing during render.

## Relationship to chat and render

`@ngaf/chat` detects A2UI content in assistant output, feeds the JSONL stream into `createA2uiMessageParser()`, applies messages to its surface store, and renders those surfaces through the A2UI render components.

`@ngaf/render` owns Angular component resolution, event dispatch, state updates, and handler execution. The A2UI package only describes protocol shapes and helper behavior.

That separation matters when debugging:

- If JSONL chunks are not becoming messages, inspect `@ngaf/a2ui`.
- If messages are not becoming surfaces, inspect the chat A2UI surface store.
- If components render incorrectly or handlers do not run, inspect the render/chat integration.

## Safe fallback posture

The parser and resolver are deliberately conservative:

- malformed JSONL lines are skipped;
- unknown envelope keys are ignored;
- missing data-model paths resolve to `undefined`;
- unrecognized dynamic-value shapes pass through unchanged.

This makes the protocol layer suitable for streaming, but it is not a full schema validator. If you accept untrusted agent output, validate the payload at your boundary before wiring it to privileged handlers.

## Install

```bash
npm install @ngaf/a2ui
```

The package has no peer dependencies.
119 changes: 119 additions & 0 deletions apps/website/content/docs/a2ui/reference/parser-resolver-guards.mdx
Original file line number Diff line number Diff line change
@@ -0,0 +1,119 @@
# Parser, Resolver, and Guards

`@ngaf/a2ui` exports small helpers that keep stream parsing and dynamic value resolution consistent across packages.

## createA2uiMessageParser()

```ts
import { createA2uiMessageParser } from '@ngaf/a2ui';

const parser = createA2uiMessageParser();
const messages = parser.push(chunk);
```

`push(chunk)` appends the chunk to an internal buffer and returns every complete message found before the last newline.

Important behavior from source:

- the parser is JSONL-based;
- a complete message requires a trailing newline;
- CRLF works because each line is trimmed;
- empty lines are ignored;
- malformed lines are skipped silently;
- unknown top-level envelopes are ignored;
- multiple messages can be returned from one chunk.

The parser checks only for the known envelope key and a non-null object value. It does not validate each nested field.

## resolveDynamic()

```ts
import { resolveDynamic } from '@ngaf/a2ui';

const model = {
customer: { name: 'Ada' },
count: 2,
};

resolveDynamic({ path: '/customer/name' }, model); // "Ada"
resolveDynamic({ literalNumber: 2 }, model); // 2
```

`resolveDynamic(value, model, scope?)` handles:

| Input shape | Result |
|-------------|--------|
| `{ literalString }` | the wrapped string |
| `{ literalNumber }` | the wrapped number |
| `{ literalBoolean }` | the wrapped boolean |
| `{ literalArray }` | the wrapped array |
| `{ path }` | the value at that model path |
| arrays | recursively resolved array values |
| `null` or `undefined` | returned as-is |
| unrecognized shapes | returned as-is |

Absolute paths start with `/`.

Relative paths resolve against `scope.basePath` when a scope is supplied. Without a scope, a relative path is treated as root-relative by prefixing `/`.

```ts
resolveDynamic(
{ path: 'name' },
{ items: [{ name: 'Ada' }] },
{ basePath: '/items/0', item: { name: 'Ada' } },
); // "Ada"
```

`A2uiScope.item` is part of the public type, but the current resolver only uses `basePath`.

## Pointer helpers

```ts
import { getByPointer, setByPointer, deleteByPointer } from '@ngaf/a2ui';
```

The pointer helpers use slash-separated paths:

```ts
const model = { customer: { name: 'Ada' } };

getByPointer(model, '/customer/name'); // "Ada"
setByPointer(model, '/customer/name', 'Grace');
deleteByPointer(model, '/customer/name');
```

Current behavior is intentionally small:

- empty pointer and `/` point at the root;
- missing paths read as `undefined`;
- `setByPointer()` returns a cloned object path rather than mutating the original root;
- `deleteByPointer()` returns the original model when the parent path does not exist.

These helpers do not implement full RFC 6901 escaping semantics. Avoid keys that require `~0` or `~1` escaping unless you normalize them before they enter A2UI state.

## Guards

The public guards are:

```ts
isLiteralString(value)
isLiteralNumber(value)
isLiteralBoolean(value)
isPathRef(value)
```

They are shape checks for dynamic wrapper objects. The literal guards check for the presence of the wrapper key. `isPathRef()` also verifies that `path` is a string.

Use them when you need to branch on protocol values without importing internal renderer code.

## Validation vs handler wiring

This package does not run validation rules, map actions to Angular handlers, or call user functions. It gives you typed values and parsing helpers.

A practical boundary is:

- use `@ngaf/a2ui` to parse and inspect the protocol stream;
- use app or server validation to decide whether a message is trusted;
- use `@ngaf/chat` and `@ngaf/render` to display surfaces and wire interactions.

That split keeps protocol parsing deterministic and keeps privileged behavior in the host application.
203 changes: 203 additions & 0 deletions apps/website/content/docs/a2ui/reference/schema.mdx
Original file line number Diff line number Diff line change
@@ -0,0 +1,203 @@
# A2UI Schema

The `@ngaf/a2ui` schema is a TypeScript model of the protocol shapes used by the framework. It is useful as a contract for agent output and custom integrations, but it is not a runtime validator.

## Dynamic values

Dynamic values are wrapped objects. A value can be literal or resolved from the surface data model by path.

```ts
type DynamicString =
| { literalString: string }
| { path: string };

type DynamicNumber =
| { literalNumber: number }
| { path: string };

type DynamicBoolean =
| { literalBoolean: boolean }
| { path: string };

type DynamicStringList =
| { literalArray: string[] }
| { path: string };
```

Absolute paths start with `/` and are resolved from the model root. Relative paths are resolved from an optional `A2uiScope`.

## Children

Layout components use either explicit child IDs or a template declaration.

```ts
type A2uiChildren =
| { explicitList: string[] }
| { template: { componentId: string; dataBinding: string } };
```

The protocol layer only types this shape. Template expansion is renderer behavior.

## Actions

An action has a name and optional context entries. Context values use the same dynamic wrappers as component props.

```ts
interface A2uiAction {
name: string;
context?: A2uiActionContextEntry[];
}
```

`@ngaf/a2ui` does not execute actions. It only describes the payload that chat/render code can turn into an outbound `A2uiActionMessage`.

## Components

Every component has an `id`, optional `weight`, and a single-key `component` union.

```ts
interface A2uiComponent {
id: string;
weight?: number;
component: A2uiComponentDef;
}
```

The exported component definitions are:

| Definition | Main fields |
|------------|-------------|
| `Text` | `text`, `usageHint` |
| `Image` | `url`, `alt`, `width`, `height` |
| `Icon` | `icon`, `size` |
| `Video` | `url`, `autoPlay`, `controls` |
| `AudioPlayer` | `url`, `autoPlay`, `controls` |
| `Row` | `children`, `gap`, `alignment`, `distribution` |
| `Column` | `children`, `gap`, `alignment` |
| `List` | `children`, `direction` |
| `Card` | `child` |
| `Tabs` | `tabItems` |
| `Divider` | `direction` |
| `Modal` | `entryPointChild`, `contentChild`, `title` |
| `Button` | `child`, `primary`, `action` |
| `CheckBox` | `label`, `checked`, `action` |
| `TextField` | `label`, `text`, `textFieldType`, `validationRegexp` |
| `DateTimeInput` | `label`, `value`, `enableDate`, `enableTime` |
| `MultipleChoice` | `selections`, `options`, `maxAllowedSelections`, `label` |
| `Slider` | `value`, `minValue`, `maxValue`, `step`, `label` |

The schema exposes `validationRegexp` on `TextField`, but validation execution is not implemented in this package. Treat schema fields as protocol data until a renderer wires behavior.

## Message envelopes

The parser recognizes four top-level envelopes:

```ts
type A2uiMessage =
| { surfaceUpdate: A2uiSurfaceUpdate }
| { dataModelUpdate: A2uiDataModelUpdate }
| { beginRendering: A2uiBeginRendering }
| { deleteSurface: A2uiDeleteSurface };
```

### surfaceUpdate

Adds or replaces components for a surface.

```json
{
"surfaceUpdate": {
"surfaceId": "checkout",
"components": [
{ "id": "root", "component": { "Card": { "child": "title" } } },
{ "id": "title", "component": { "Text": { "text": { "literalString": "Checkout" } } } }
]
}
}
```

### dataModelUpdate

Carries nested data-model entries. `path` is optional.

```json
{
"dataModelUpdate": {
"surfaceId": "checkout",
"path": "/customer",
"contents": [
{ "key": "name", "valueString": "Ada" },
{ "key": "active", "valueBoolean": true }
]
}
}
```

Each `A2uiDataModelEntry` has a `key` plus one of `valueString`, `valueNumber`, `valueBoolean`, or `valueMap`.

### beginRendering

Identifies the root component to render for a surface.

```json
{
"beginRendering": {
"surfaceId": "checkout",
"root": "root",
"styles": {
"font": "Inter",
"primaryColor": "#2563eb"
}
}
}
```

The source comments describe `styles.font` and `styles.primaryColor` as the canonical style fields.

### deleteSurface

Removes a surface by ID.

```json
{ "deleteSurface": { "surfaceId": "checkout" } }
```

## Internal surface model

`A2uiSurface` is an internal model used after messages are applied:

```ts
interface A2uiSurface {
surfaceId: string;
catalogId: string;
theme?: A2uiTheme;
sendDataModel?: boolean;
components: Map<string, A2uiComponent>;
dataModel: Record<string, unknown>;
styles?: { font?: string; primaryColor?: string };
}
```

This shape is not constrained to the wire format. Do not assume an agent sends it directly.

## Outbound action messages

When a rendered surface sends an action back to the agent, the typed outbound shape is:

```ts
interface A2uiActionMessage {
version: 'v0.9';
action: {
name: string;
surfaceId: string;
sourceComponentId: string;
timestamp: string;
context: Record<string, unknown>;
};
metadata?: {
a2uiClientDataModel: A2uiClientDataModel;
};
}
```

The outbound action version is currently typed as `v0.9` in source.
Loading
Loading