diff --git a/apps/website/content/docs/a2ui/api/api-docs.json b/apps/website/content/docs/a2ui/api/api-docs.json index 7005841de..ed322a41f 100644 --- a/apps/website/content/docs/a2ui/api/api-docs.json +++ b/apps/website/content/docs/a2ui/api/api-docs.json @@ -723,6 +723,35 @@ ], "examples": [] }, + { + "name": "A2uiFunctionContext", + "kind": "interface", + "description": "Execution context handed to every function implementation.", + "properties": [ + { + "name": "locale", + "type": "string", + "description": "BCP 47 locale for Intl-based formatting; host default when undefined.", + "optional": true + } + ], + "methods": [ + { + "name": "resolveArg", + "signature": "resolveArg(value: unknown): unknown", + "description": "Resolve a (possibly dynamic) argument value — bare literal, `{ path }`\nbinding, or nested `{ call }` — against the current data model/scope.", + "params": [ + { + "name": "value", + "type": "unknown", + "description": "", + "optional": false + } + ] + } + ], + "examples": [] + }, { "name": "A2uiIcon", "kind": "interface", @@ -1465,6 +1494,20 @@ "signature": "A2uiCatalogComponent | A2uiComponentBase & Record", "examples": [] }, + { + "name": "A2uiFunctionImpl", + "kind": "type", + "description": "", + "signature": "(args: Record, ctx: A2uiFunctionContext) => unknown", + "examples": [] + }, + { + "name": "A2uiFunctionRegistry", + "kind": "type", + "description": "", + "signature": "ReadonlyMap", + "examples": [] + }, { "name": "A2uiMessage", "kind": "type", @@ -1528,6 +1571,27 @@ "signature": "\"v0.9\"", "examples": [] }, + { + "name": "createA2uiFunctionRegistry", + "kind": "function", + "description": "Creates an A2UI client-side function registry containing the standard\nbasic-catalog functions (`formatString`, `formatNumber`, `formatCurrency`,\n`formatDate`, `pluralize`, `and`, `or`, `not`), optionally extended or\noverridden with custom implementations.", + "signature": "createA2uiFunctionRegistry(overrides: Record): A2uiFunctionRegistry", + "params": [ + { + "name": "overrides", + "type": "Record", + "description": "", + "optional": true + } + ], + "returns": { + "type": "A2uiFunctionRegistry", + "description": "" + }, + "examples": [ + "```ts\nconst registry = createA2uiFunctionRegistry();\nresolveDynamic({ call: 'formatCurrency', args: { value: 42, currency: 'USD' } }, {}, undefined, registry);\n```" + ] + }, { "name": "createA2uiMessageParser", "kind": "function", @@ -1637,8 +1701,8 @@ { "name": "resolveDynamic", "kind": "function", - "description": "Resolves an A2UI v0.9 dynamic value against a client data model.\n\nBare literals (strings, numbers, booleans) pass through unchanged, `{ path }`\nreferences read from the model by JSON-pointer path, arrays resolve\nelement-wise, and client-side function calls (`{ call }`) resolve to\n`undefined` until function execution ships. Unrecognized plain objects pass\nthrough unchanged.", - "signature": "resolveDynamic(value: unknown, model: Record, scope: A2uiScope): unknown", + "description": "Resolves an A2UI v0.9 dynamic value against a client data model.\n\nBare literals (strings, numbers, booleans) pass through unchanged, `{ path }`\nreferences read from the model by JSON-pointer path, arrays resolve\nelement-wise, and client-side function calls (`{ call }`) execute through the\nprovided function registry — argument values resolve recursively, so args may\nthemselves be bindings or nested calls. Without a registry (or for unknown\nfunction names) calls resolve to `undefined`. Unrecognized plain objects pass\nthrough unchanged.", + "signature": "resolveDynamic(value: unknown, model: Record, scope: A2uiScope, registry: A2uiFunctionRegistry): unknown", "params": [ { "name": "value", @@ -1657,6 +1721,12 @@ "type": "A2uiScope", "description": "", "optional": true + }, + { + "name": "registry", + "type": "A2uiFunctionRegistry", + "description": "", + "optional": true } ], "returns": { @@ -1664,7 +1734,7 @@ "description": "" }, "examples": [ - "```ts\nconst model = { customer: { name: 'Ada' } };\nresolveDynamic({ path: '/customer/name' }, model); // 'Ada'\nresolveDynamic('Checkout', model); // 'Checkout'\n```" + "```ts\nconst model = { customer: { name: 'Ada' } };\nresolveDynamic({ path: '/customer/name' }, model); // 'Ada'\nresolveDynamic('Checkout', model); // 'Checkout'\nresolveDynamic(\n { call: 'formatString', args: { value: 'Hi ${/customer/name}' } },\n model, undefined, createA2uiFunctionRegistry(),\n); // 'Hi Ada'\n```" ] }, { diff --git a/apps/website/content/docs/a2ui/getting-started/introduction.mdx b/apps/website/content/docs/a2ui/getting-started/introduction.mdx index 7d0027f55..0071a07a9 100644 --- a/apps/website/content/docs/a2ui/getting-started/introduction.mdx +++ b/apps/website/content/docs/a2ui/getting-started/introduction.mdx @@ -63,7 +63,7 @@ The parser and resolver are deliberately conservative: - unknown envelope keys are ignored (forward compatibility with future protocol versions); - missing data-model paths resolve to `undefined`; - unrecognized dynamic-value shapes pass through unchanged; -- `{ call: ... }` function-call values resolve to `undefined` until client-side function execution ships. +- `{ call: ... }` function-call values execute through the standard function registry when one is passed to `resolveDynamic`; without a registry (or for unknown names) they resolve to `undefined`. 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. diff --git a/apps/website/content/docs/a2ui/getting-started/quickstart.mdx b/apps/website/content/docs/a2ui/getting-started/quickstart.mdx index 1105ecec0..77c7b65de 100644 --- a/apps/website/content/docs/a2ui/getting-started/quickstart.mdx +++ b/apps/website/content/docs/a2ui/getting-started/quickstart.mdx @@ -103,7 +103,7 @@ resolveDynamic('Search flights', model); // "Search flights" resolveDynamic({ path: '/missing' }, model); // undefined ``` -A bare literal (string, number, boolean) passes through unchanged. A `{ path }` reads from the model by JSON pointer. A missing path resolves to `undefined` rather than throwing — same conservative posture as the parser. A `{ call }` function-call value resolves to `undefined` until client-side function execution ships. +A bare literal (string, number, boolean) passes through unchanged. A `{ path }` reads from the model by JSON pointer. A missing path resolves to `undefined` rather than throwing — same conservative posture as the parser. A `{ call }` function-call value executes through a function registry (`createA2uiFunctionRegistry()`) when one is supplied; without one it resolves to `undefined`. ## Conclusion diff --git a/apps/website/content/docs/a2ui/guides/data-model.mdx b/apps/website/content/docs/a2ui/guides/data-model.mdx index 13ab67b65..142237d58 100644 --- a/apps/website/content/docs/a2ui/guides/data-model.mdx +++ b/apps/website/content/docs/a2ui/guides/data-model.mdx @@ -94,7 +94,7 @@ Nesting is just JSON: `value: { name: 'Ada', address: { city: 'London' } }` writ 1. `null` / `undefined` pass through as-is. 2. Arrays are mapped recursively — each element resolved in turn. -3. A `{ call }` function-call value resolves to `undefined` (client-side function execution ships in an upcoming release). Checked before path refs so a call's `args` never masquerade as a binding. +3. A `{ call }` function-call value executes through the function registry passed to `resolveDynamic` (standard set: `formatString`, `formatNumber`, `formatCurrency`, `formatDate`, `pluralize`, `and`, `or`, `not`); args resolve recursively, so they may be bindings or nested calls. Without a registry, or for unknown names, the value resolves to `undefined`. Checked before path refs so a call's `args` never masquerade as a binding. 4. A `{ path }` reference reads from the model. 5. Anything else — a bare string, number, boolean, or plain object — passes through unchanged. Bare values *are* the v0.9 literal form; there are no wrapper objects. diff --git a/apps/website/content/docs/a2ui/guides/message-protocol.mdx b/apps/website/content/docs/a2ui/guides/message-protocol.mdx index 0412cf26d..bea9f91b9 100644 --- a/apps/website/content/docs/a2ui/guides/message-protocol.mdx +++ b/apps/website/content/docs/a2ui/guides/message-protocol.mdx @@ -54,7 +54,7 @@ A reference is `{"path":"/origin"}` — a JSON pointer into the surface's data m {"text":{"path":"/headline"}} ``` -A function call is `{"call":"formatDate","args":{...}}` — a typed invocation of a client-side catalog function (`formatString`, `formatCurrency`, `required`, ...). Function calls are part of the wire format today; `resolveDynamic` resolves them to `undefined` until function execution ships in an upcoming release. +A function call is `{"call":"formatDate","args":{...}}` — a typed invocation of a client-side catalog function (`formatString`, `formatCurrency`, `required`, ...). Function calls execute client-side: pass `createA2uiFunctionRegistry()` as the fourth argument to `resolveDynamic` and the standard formatting/logic functions run with recursively-resolved args. Unknown names resolve to `undefined` (with a one-time console warning). ## What are the four envelopes? diff --git a/apps/website/content/docs/a2ui/reference/parser-resolver-guards.mdx b/apps/website/content/docs/a2ui/reference/parser-resolver-guards.mdx index 61fb261fb..1870304bf 100644 --- a/apps/website/content/docs/a2ui/reference/parser-resolver-guards.mdx +++ b/apps/website/content/docs/a2ui/reference/parser-resolver-guards.mdx @@ -46,7 +46,7 @@ resolveDynamic(2, model); // 2 |-------------|--------| | bare literal (string, number, boolean) | returned as-is | | `{ path }` | the value at that model path | -| `{ call }` | `undefined` — client-side function execution ships in an upcoming release | +| `{ call }` | executes via the `A2uiFunctionRegistry` passed as the fourth argument (`createA2uiFunctionRegistry()` provides the standard set); `undefined` without a registry or for unknown names | | arrays | recursively resolved array values | | `null` or `undefined` | returned as-is | | unrecognized plain objects | returned as-is | diff --git a/apps/website/content/docs/a2ui/reference/schema.mdx b/apps/website/content/docs/a2ui/reference/schema.mdx index e7a7b7ae5..a463bd7f8 100644 --- a/apps/website/content/docs/a2ui/reference/schema.mdx +++ b/apps/website/content/docs/a2ui/reference/schema.mdx @@ -34,7 +34,7 @@ type DynamicBoolean = boolean | A2uiPathRef | A2uiFunctionCall; type DynamicStringList = string[] | A2uiPathRef | A2uiFunctionCall; ``` -Absolute paths start with `/` and are resolved from the model root. Relative paths are resolved from an optional `A2uiScope` (used inside children templates). Function calls are typed on the wire today; execution ships in an upcoming release, so `resolveDynamic` returns `undefined` for them. +Absolute paths start with `/` and are resolved from the model root. Relative paths are resolved from an optional `A2uiScope` (used inside children templates). Function calls execute through an `A2uiFunctionRegistry` (see `createA2uiFunctionRegistry`); `resolveDynamic` returns `undefined` for them only when no registry is supplied or the name is unknown. ## Children diff --git a/apps/website/content/docs/chat/a2ui/catalog.mdx b/apps/website/content/docs/chat/a2ui/catalog.mdx index a7e37d848..36e6b75d1 100644 --- a/apps/website/content/docs/chat/a2ui/catalog.mdx +++ b/apps/website/content/docs/chat/a2ui/catalog.mdx @@ -431,7 +431,7 @@ Renders an HTML5 `