@lynx-js/genui 0.0.2 → 0.0.4
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/README.md +5 -9
- package/a2ui/AGENTS.md +167 -0
- package/a2ui/README.md +76 -780
- package/a2ui/README_zh.md +103 -0
- package/a2ui/dist/catalog/Button/catalog.json +2 -1
- package/a2ui/dist/catalog/Button/index.d.ts +5 -0
- package/a2ui/dist/catalog/Button/index.jsx +3 -0
- package/a2ui/dist/catalog/Button/index.jsx.map +1 -1
- package/a2ui/dist/catalog/Card/catalog.json +2 -1
- package/a2ui/dist/catalog/Card/index.d.ts +5 -0
- package/a2ui/dist/catalog/Card/index.jsx +3 -0
- package/a2ui/dist/catalog/Card/index.jsx.map +1 -1
- package/a2ui/dist/catalog/CheckBox/catalog.json +2 -1
- package/a2ui/dist/catalog/CheckBox/index.d.ts +5 -0
- package/a2ui/dist/catalog/CheckBox/index.jsx +3 -0
- package/a2ui/dist/catalog/CheckBox/index.jsx.map +1 -1
- package/a2ui/dist/catalog/ChoicePicker/catalog.json +2 -1
- package/a2ui/dist/catalog/ChoicePicker/index.d.ts +5 -0
- package/a2ui/dist/catalog/ChoicePicker/index.jsx +3 -0
- package/a2ui/dist/catalog/ChoicePicker/index.jsx.map +1 -1
- package/a2ui/dist/catalog/Column/catalog.json +2 -1
- package/a2ui/dist/catalog/Column/index.d.ts +5 -0
- package/a2ui/dist/catalog/Column/index.jsx +3 -0
- package/a2ui/dist/catalog/Column/index.jsx.map +1 -1
- package/a2ui/dist/catalog/DateTimeInput/catalog.json +2 -1
- package/a2ui/dist/catalog/DateTimeInput/index.d.ts +5 -0
- package/a2ui/dist/catalog/DateTimeInput/index.jsx +4 -10
- package/a2ui/dist/catalog/DateTimeInput/index.jsx.map +1 -1
- package/a2ui/dist/catalog/DateTimeInput/utils.d.ts +0 -1
- package/a2ui/dist/catalog/DateTimeInput/utils.js +0 -3
- package/a2ui/dist/catalog/DateTimeInput/utils.js.map +1 -1
- package/a2ui/dist/catalog/Divider/catalog.json +2 -1
- package/a2ui/dist/catalog/Divider/index.d.ts +5 -0
- package/a2ui/dist/catalog/Divider/index.jsx +3 -0
- package/a2ui/dist/catalog/Divider/index.jsx.map +1 -1
- package/a2ui/dist/catalog/Icon/catalog.json +2 -1
- package/a2ui/dist/catalog/Icon/index.d.ts +5 -0
- package/a2ui/dist/catalog/Icon/index.jsx +3 -0
- package/a2ui/dist/catalog/Icon/index.jsx.map +1 -1
- package/a2ui/dist/catalog/Image/catalog.json +2 -1
- package/a2ui/dist/catalog/Image/index.d.ts +5 -0
- package/a2ui/dist/catalog/Image/index.jsx +3 -0
- package/a2ui/dist/catalog/Image/index.jsx.map +1 -1
- package/a2ui/dist/catalog/LazyComponent/catalog.json +37 -0
- package/a2ui/dist/catalog/LazyComponent/index.d.ts +27 -0
- package/a2ui/dist/catalog/LazyComponent/index.jsx +39 -0
- package/a2ui/dist/catalog/LazyComponent/index.jsx.map +1 -0
- package/a2ui/dist/catalog/LineChart/catalog.json +2 -1
- package/a2ui/dist/catalog/LineChart/index.d.ts +8 -0
- package/a2ui/dist/catalog/LineChart/index.jsx +3 -0
- package/a2ui/dist/catalog/LineChart/index.jsx.map +1 -1
- package/a2ui/dist/catalog/List/catalog.json +2 -1
- package/a2ui/dist/catalog/List/index.d.ts +5 -0
- package/a2ui/dist/catalog/List/index.jsx +3 -0
- package/a2ui/dist/catalog/List/index.jsx.map +1 -1
- package/a2ui/dist/catalog/Loading/catalog.json +16 -0
- package/a2ui/dist/catalog/Loading/index.d.ts +15 -0
- package/a2ui/dist/catalog/Loading/index.jsx +14 -0
- package/a2ui/dist/catalog/Loading/index.jsx.map +1 -0
- package/a2ui/dist/catalog/Modal/catalog.json +2 -1
- package/a2ui/dist/catalog/Modal/index.d.ts +5 -0
- package/a2ui/dist/catalog/Modal/index.jsx +3 -0
- package/a2ui/dist/catalog/Modal/index.jsx.map +1 -1
- package/a2ui/dist/catalog/PieChart/catalog.json +2 -1
- package/a2ui/dist/catalog/PieChart/index.d.ts +8 -0
- package/a2ui/dist/catalog/PieChart/index.jsx +3 -0
- package/a2ui/dist/catalog/PieChart/index.jsx.map +1 -1
- package/a2ui/dist/catalog/RadioGroup/catalog.json +2 -1
- package/a2ui/dist/catalog/RadioGroup/index.d.ts +5 -0
- package/a2ui/dist/catalog/RadioGroup/index.jsx +3 -0
- package/a2ui/dist/catalog/RadioGroup/index.jsx.map +1 -1
- package/a2ui/dist/catalog/Row/catalog.json +2 -1
- package/a2ui/dist/catalog/Row/index.d.ts +5 -0
- package/a2ui/dist/catalog/Row/index.jsx +3 -0
- package/a2ui/dist/catalog/Row/index.jsx.map +1 -1
- package/a2ui/dist/catalog/Slider/catalog.json +2 -1
- package/a2ui/dist/catalog/Slider/index.d.ts +5 -0
- package/a2ui/dist/catalog/Slider/index.jsx +3 -0
- package/a2ui/dist/catalog/Slider/index.jsx.map +1 -1
- package/a2ui/dist/catalog/Tabs/catalog.json +2 -1
- package/a2ui/dist/catalog/Tabs/index.d.ts +5 -0
- package/a2ui/dist/catalog/Tabs/index.jsx +3 -0
- package/a2ui/dist/catalog/Tabs/index.jsx.map +1 -1
- package/a2ui/dist/catalog/Text/catalog.json +2 -1
- package/a2ui/dist/catalog/Text/index.d.ts +5 -0
- package/a2ui/dist/catalog/Text/index.jsx +3 -0
- package/a2ui/dist/catalog/Text/index.jsx.map +1 -1
- package/a2ui/dist/catalog/TextField/catalog.json +2 -1
- package/a2ui/dist/catalog/TextField/index.d.ts +5 -0
- package/a2ui/dist/catalog/TextField/index.jsx +3 -0
- package/a2ui/dist/catalog/TextField/index.jsx.map +1 -1
- package/a2ui/dist/catalog/defineCatalog.d.ts +4 -4
- package/a2ui/dist/catalog/defineCatalog.js.map +1 -1
- package/a2ui/dist/catalog/index.d.ts +2 -0
- package/a2ui/dist/catalog/index.js +92 -18
- package/a2ui/dist/catalog/index.js.map +1 -1
- package/a2ui/dist/catalog.json +2603 -0
- package/a2ui/dist/index.d.ts +1 -1
- package/a2ui/dist/index.js +2 -2
- package/a2ui/dist/index.js.map +1 -1
- package/a2ui/dist/react/A2UI.d.ts +3 -0
- package/a2ui/dist/react/A2UI.jsx.map +1 -1
- package/a2ui/dist/react/A2UIRenderer.d.ts +5 -18
- package/a2ui/dist/react/A2UIRenderer.jsx +3 -13
- package/a2ui/dist/react/A2UIRenderer.jsx.map +1 -1
- package/a2ui/dist/react/useAction.d.ts +7 -0
- package/a2ui/dist/react/useAction.js +4 -0
- package/a2ui/dist/react/useAction.js.map +1 -1
- package/a2ui/dist/react/useDataBinding.d.ts +7 -5
- package/a2ui/dist/react/useDataBinding.js +18 -0
- package/a2ui/dist/react/useDataBinding.js.map +1 -1
- package/a2ui/dist/store/FunctionRegistry.d.ts +7 -0
- package/a2ui/dist/store/FunctionRegistry.js +4 -0
- package/a2ui/dist/store/FunctionRegistry.js.map +1 -1
- package/a2ui/dist/store/MessageProcessor.d.ts +7 -0
- package/a2ui/dist/store/MessageProcessor.js +4 -0
- package/a2ui/dist/store/MessageProcessor.js.map +1 -1
- package/a2ui/dist/store/MessageStore.d.ts +6 -0
- package/a2ui/dist/store/MessageStore.js +3 -0
- package/a2ui/dist/store/MessageStore.js.map +1 -1
- package/a2ui/dist/store/Resource.d.ts +8 -0
- package/a2ui/dist/store/Resource.js +4 -0
- package/a2ui/dist/store/Resource.js.map +1 -1
- package/a2ui/dist/store/SignalStore.d.ts +3 -2
- package/a2ui/dist/store/SignalStore.js +9 -0
- package/a2ui/dist/store/SignalStore.js.map +1 -1
- package/a2ui/dist/store/resolveDynamic.d.ts +4 -3
- package/a2ui/dist/store/resolveDynamic.js +19 -0
- package/a2ui/dist/store/resolveDynamic.js.map +1 -1
- package/a2ui/dist/store/resolveFunctionCall.d.ts +7 -0
- package/a2ui/dist/store/resolveFunctionCall.js +4 -0
- package/a2ui/dist/store/resolveFunctionCall.js.map +1 -1
- package/a2ui/dist/store/types.d.ts +13 -0
- package/a2ui/dist/store/utils.d.ts +6 -4
- package/a2ui/dist/store/utils.js +26 -0
- package/a2ui/dist/store/utils.js.map +1 -1
- package/a2ui/dist/tsconfig.build.tsbuildinfo +1 -1
- package/a2ui/docs/catalog-guide.md +407 -0
- package/a2ui/docs/catalog-guide_zh.md +379 -0
- package/a2ui/docs/overview.md +312 -0
- package/a2ui/docs/overview_zh.md +289 -0
- package/a2ui/docs/system-prompts.md +187 -0
- package/a2ui/docs/system-prompts_zh.md +187 -0
- package/a2ui/src/catalog/README.md +12 -0
- package/a2ui/src/catalog/index.ts +52 -0
- package/a2ui/src/catalog/readme_zh.md +11 -0
- package/a2ui/src/index.ts +116 -0
- package/a2ui/styles/catalog/Button.css +5 -5
- package/a2ui/styles/catalog/DateTimeInput.css +22 -30
- package/a2ui/styles/catalog/Loading.css +61 -0
- package/a2ui-catalog-extractor/README.md +14 -7
- package/a2ui-catalog-extractor/dist/cli.d.ts +1 -0
- package/a2ui-catalog-extractor/dist/cli.js +15 -6
- package/a2ui-catalog-extractor/dist/cli.js.map +1 -1
- package/a2ui-catalog-extractor/dist/index.d.ts +97 -2
- package/a2ui-catalog-extractor/dist/index.js +91 -6
- package/a2ui-catalog-extractor/dist/index.js.map +1 -1
- package/a2ui-catalog-extractor/dist/tsconfig.build.tsbuildinfo +1 -1
- package/a2ui-catalog-extractor/skills/a2ui-catalog-extractor/SKILL.md +1 -1
- package/a2ui-prompt/README.md +3 -2
- package/a2ui-prompt/dist/index.d.ts +41 -0
- package/a2ui-prompt/dist/index.js +159 -84
- package/cli/README.md +26 -0
- package/cli/bin/cli.js +7 -265
- package/cli/dist/a2ui/create.d.ts +5 -0
- package/cli/dist/a2ui/create.js +178 -0
- package/cli/dist/a2ui/create.js.map +1 -0
- package/cli/dist/a2ui/index.d.ts +5 -0
- package/cli/dist/a2ui/index.js +170 -0
- package/cli/dist/a2ui/index.js.map +1 -0
- package/cli/dist/cli.d.ts +4 -0
- package/cli/dist/cli.js +40 -0
- package/cli/dist/cli.js.map +1 -0
- package/cli/dist/openui.d.ts +1 -0
- package/cli/dist/openui.js +21 -0
- package/cli/dist/openui.js.map +1 -0
- package/cli/dist/tsconfig.build.tsbuildinfo +1 -0
- package/cli/dist/utils.d.ts +2 -0
- package/cli/dist/utils.js +17 -0
- package/cli/dist/utils.js.map +1 -0
- package/cli/templates/default/lynx.config.ts +13 -0
- package/cli/templates/default/package.json +27 -0
- package/cli/templates/default/src/App.css +88 -0
- package/cli/templates/default/src/App.tsx +100 -0
- package/cli/templates/default/src/index.tsx +10 -0
- package/cli/templates/default/src/messages.ts +158 -0
- package/cli/templates/default/src/rspeedy-env.d.ts +14 -0
- package/cli/templates/default/src/tsconfig.json +17 -0
- package/cli/templates/default/tsconfig.json +15 -0
- package/cli/templates/default/tsconfig.node.json +16 -0
- package/dist/index.d.ts +2 -0
- package/dist/index.js +1 -0
- package/dist/index.js.map +1 -1
- package/dist/tsconfig.build.tsbuildinfo +1 -1
- package/index.ts +12 -0
- package/openui/README.md +50 -46
- package/openui/dist/catalog/Action/{index.js → index.jsx} +1 -1
- package/openui/dist/catalog/Action/index.jsx.map +1 -0
- package/openui/dist/catalog/Button/index.d.ts +8 -8
- package/openui/dist/catalog/Button/{index.js → index.jsx} +28 -14
- package/openui/dist/catalog/Button/index.jsx.map +1 -0
- package/openui/dist/catalog/Card/index.d.ts +1 -1
- package/openui/dist/catalog/Card/{index.js → index.jsx} +5 -4
- package/openui/dist/catalog/Card/{index.js.map → index.jsx.map} +1 -1
- package/openui/dist/catalog/CardHeader/index.d.ts +1 -1
- package/openui/dist/catalog/CardHeader/index.jsx +20 -0
- package/openui/dist/catalog/CardHeader/index.jsx.map +1 -0
- package/openui/dist/catalog/CheckBox/index.d.ts +16 -0
- package/openui/dist/catalog/CheckBox/index.jsx +82 -0
- package/openui/dist/catalog/CheckBox/index.jsx.map +1 -0
- package/openui/dist/catalog/Icon/index.d.ts +44 -0
- package/openui/dist/catalog/Icon/index.jsx +66 -0
- package/openui/dist/catalog/Icon/index.jsx.map +1 -0
- package/openui/dist/catalog/Image/index.d.ts +19 -0
- package/openui/dist/catalog/Image/index.jsx +40 -0
- package/openui/dist/catalog/Image/index.jsx.map +1 -0
- package/openui/dist/catalog/Loading/index.d.ts +7 -0
- package/openui/dist/catalog/Loading/index.jsx +25 -0
- package/openui/dist/catalog/Loading/index.jsx.map +1 -0
- package/openui/dist/catalog/RadioGroup/index.d.ts +21 -0
- package/openui/dist/catalog/RadioGroup/index.jsx +99 -0
- package/openui/dist/catalog/RadioGroup/index.jsx.map +1 -0
- package/openui/dist/catalog/Separator/index.d.ts +1 -1
- package/openui/dist/catalog/Separator/{index.js → index.jsx} +3 -4
- package/openui/dist/catalog/Separator/index.jsx.map +1 -0
- package/openui/dist/catalog/Slider/index.d.ts +19 -0
- package/openui/dist/catalog/Slider/index.jsx +139 -0
- package/openui/dist/catalog/Slider/index.jsx.map +1 -0
- package/openui/dist/catalog/Stack/index.d.ts +1 -1
- package/openui/dist/catalog/Stack/{index.js → index.jsx} +3 -4
- package/openui/dist/catalog/Stack/{index.js.map → index.jsx.map} +1 -1
- package/openui/dist/catalog/Tag/index.d.ts +1 -1
- package/openui/dist/catalog/Tag/{index.js → index.jsx} +5 -4
- package/openui/dist/catalog/Tag/index.jsx.map +1 -0
- package/openui/dist/catalog/TextContent/index.d.ts +1 -1
- package/openui/dist/catalog/TextContent/{index.js → index.jsx} +5 -4
- package/openui/dist/catalog/TextContent/{index.js.map → index.jsx.map} +1 -1
- package/openui/dist/catalog/TextField/index.d.ts +23 -0
- package/openui/dist/catalog/TextField/index.jsx +132 -0
- package/openui/dist/catalog/TextField/index.jsx.map +1 -0
- package/openui/dist/catalog/index.d.ts +14 -7
- package/openui/dist/catalog/index.js +14 -7
- package/openui/dist/catalog/index.js.map +1 -1
- package/openui/dist/core/context.d.ts +22 -8
- package/openui/dist/core/{context.js → context.jsx} +10 -3
- package/openui/dist/core/context.jsx.map +1 -0
- package/openui/dist/core/createLibrary.d.ts +8 -1
- package/openui/dist/core/{createLibrary.js → createLibrary.jsx} +18 -3
- package/openui/dist/core/createLibrary.jsx.map +1 -0
- package/openui/dist/core/hooks/index.d.ts +1 -0
- package/openui/dist/core/hooks/index.js +1 -0
- package/openui/dist/core/hooks/index.js.map +1 -1
- package/openui/dist/core/hooks/useFormValidation.d.ts +9 -0
- package/openui/dist/core/hooks/useFormValidation.js +6 -0
- package/openui/dist/core/hooks/useFormValidation.js.map +1 -1
- package/openui/dist/core/hooks/useOpenUIState.d.ts +8 -2
- package/openui/dist/core/hooks/useOpenUIState.js +3 -1
- package/openui/dist/core/hooks/useOpenUIState.js.map +1 -1
- package/openui/dist/core/hooks/useStateField.d.ts +3 -0
- package/openui/dist/core/hooks/useStateField.js +4 -1
- package/openui/dist/core/hooks/useStateField.js.map +1 -1
- package/openui/dist/core/index.d.ts +13 -7
- package/openui/dist/core/index.js +7 -4
- package/openui/dist/core/index.js.map +1 -1
- package/openui/dist/core/library.d.ts +6 -1
- package/openui/dist/core/{library.js → library.jsx} +9 -1
- package/openui/dist/core/library.jsx.map +1 -0
- package/openui/dist/core/renderer.css +527 -0
- package/openui/dist/core/renderer.d.ts +41 -4
- package/openui/dist/core/renderer.jsx +284 -0
- package/openui/dist/core/renderer.jsx.map +1 -0
- package/openui/dist/core/runtime/index.d.ts +1 -0
- package/openui/dist/core/runtime/index.js +5 -0
- package/openui/dist/core/runtime/index.js.map +1 -0
- package/openui/dist/core/runtime/reactive.d.ts +7 -0
- package/openui/dist/core/runtime/reactive.js +10 -0
- package/openui/dist/core/runtime/reactive.js.map +1 -0
- package/openui/dist/openui-prompt/index.d.ts +47 -0
- package/openui/dist/openui-prompt/index.js +321 -0
- package/openui/dist/openui-prompt/index.js.map +1 -0
- package/package.json +20 -8
- package/openui/dist/catalog/Action/index.js.map +0 -1
- package/openui/dist/catalog/Button/index.js.map +0 -1
- package/openui/dist/catalog/CardHeader/index.js +0 -18
- package/openui/dist/catalog/CardHeader/index.js.map +0 -1
- package/openui/dist/catalog/Separator/index.js.map +0 -1
- package/openui/dist/catalog/Tag/index.js.map +0 -1
- package/openui/dist/core/context.js.map +0 -1
- package/openui/dist/core/createLibrary.js.map +0 -1
- package/openui/dist/core/library.js.map +0 -1
- package/openui/dist/core/renderer.js +0 -139
- package/openui/dist/core/renderer.js.map +0 -1
package/a2ui/README.md
CHANGED
|
@@ -2,816 +2,112 @@
|
|
|
2
2
|
|
|
3
3
|
English | [简体中文](./README_zh.md)
|
|
4
4
|
|
|
5
|
-
|
|
6
|
-
|
|
5
|
+
`@lynx-js/genui/a2ui` is the ReactLynx client runtime for A2UI v0.9. It
|
|
6
|
+
consumes validated A2UI server-to-client JSON messages and renders trusted
|
|
7
|
+
ReactLynx components in your app.
|
|
7
8
|
|
|
8
|
-
|
|
9
|
+
Use this package when you already have, or plan to build, an Agent service that
|
|
10
|
+
returns A2UI messages. The package does not host an Agent, call an LLM, own a
|
|
11
|
+
backend route, or provide a chat shell. Your app owns the transport layer and
|
|
12
|
+
pushes messages into the renderer.
|
|
9
13
|
|
|
10
|
-
|
|
11
|
-
- In GenUI, an agent chooses from a component catalog that you publish.
|
|
12
|
-
- The client still renders real ReactLynx components. The model only sends
|
|
13
|
-
data that says which approved component to render and what props to use.
|
|
14
|
-
|
|
15
|
-
A2UI is the message protocol in the middle. It is not a replacement for React,
|
|
16
|
-
and it is not a new styling system. It is a safe, JSON-based way for an agent to
|
|
17
|
-
say: create a surface, render these components, update this data, and report
|
|
18
|
-
this user action back to the agent.
|
|
19
|
-
|
|
20
|
-
## Why It Exists
|
|
14
|
+
If you have never used A2UI before, think of it this way:
|
|
21
15
|
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
- The
|
|
25
|
-
|
|
26
|
-
- Model output is validated before the client renders it.
|
|
27
|
-
- UI can stream in progressively instead of waiting for one giant response.
|
|
28
|
-
- User actions are sent back as structured events, similar to React event
|
|
29
|
-
handlers crossing a network boundary.
|
|
16
|
+
- In React, your code chooses components and passes props.
|
|
17
|
+
- In A2UI, an Agent chooses from a component catalog that your app publishes.
|
|
18
|
+
- The client still renders real ReactLynx components. The model only sends data
|
|
19
|
+
that says which approved component to render and what props to use.
|
|
30
20
|
|
|
31
21
|
The result is not arbitrary generated code. It is a ReactLynx UI tree assembled
|
|
32
22
|
from a trusted catalog.
|
|
33
23
|
|
|
34
|
-
##
|
|
35
|
-
|
|
36
|
-
Here is the React mental model:
|
|
37
|
-
|
|
38
|
-
```tsx
|
|
39
|
-
function WeatherCard(props: WeatherCardProps) {
|
|
40
|
-
return (
|
|
41
|
-
<Card>
|
|
42
|
-
<Text>{props.city}</Text>
|
|
43
|
-
<Text>{props.temperature}</Text>
|
|
44
|
-
<Button onClick={props.onRefresh}>Refresh</Button>
|
|
45
|
-
</Card>
|
|
46
|
-
);
|
|
47
|
-
}
|
|
48
|
-
```
|
|
49
|
-
|
|
50
|
-
Here is the GenUI mental model:
|
|
51
|
-
|
|
52
|
-
1. You publish `Card`, `Text`, `Button`, and any custom components into a
|
|
53
|
-
catalog.
|
|
54
|
-
2. The agent receives the user's request and the catalog description.
|
|
55
|
-
3. The agent emits A2UI messages such as "render a Card with these children".
|
|
56
|
-
4. The client pushes those messages into a `MessageStore`.
|
|
57
|
-
5. `<A2UI>` renders the matching ReactLynx components.
|
|
58
|
-
6. When a user taps a generated button, `onAction` fires and your app sends the
|
|
59
|
-
action back to the agent.
|
|
60
|
-
|
|
61
|
-
The model never imports your code. It only names components that the renderer
|
|
62
|
-
has already allowed.
|
|
63
|
-
|
|
64
|
-
## What You Use
|
|
65
|
-
|
|
66
|
-
For a product app using A2UI, the important surfaces are:
|
|
67
|
-
|
|
68
|
-
| Surface | Role |
|
|
69
|
-
| ----------------------------- | ------------------------------------------------------------------------------------------------------------------------------------------ |
|
|
70
|
-
| `@lynx-js/genui/a2ui` | ReactLynx renderer for A2UI v0.9. It provides `<A2UI>`, `MessageStore`, catalog APIs, built-in components, and protocol helpers. |
|
|
71
|
-
| `genui a2ui` | Build-time command for generating catalog artifacts from TypeScript contracts and A2UI system prompts for your agent. |
|
|
72
|
-
| Your agent service | A backend you own. It receives user prompts/actions, calls a model with the A2UI prompt and catalog, validates output, and returns A2UI. |
|
|
73
|
-
| Your transport implementation | Client code that calls your agent service, handles REST or streaming responses, pushes messages into `MessageStore`, and forwards actions. |
|
|
74
|
-
|
|
75
|
-
## The Three Pieces
|
|
76
|
-
|
|
77
|
-
```text
|
|
78
|
-
Catalog: what can be rendered
|
|
79
|
-
-> Agent: what should be rendered
|
|
80
|
-
-> Client: render it and send actions back
|
|
81
|
-
```
|
|
82
|
-
|
|
83
|
-
### Catalog
|
|
84
|
-
|
|
85
|
-
For a React developer, the catalog is your public component API for AI. It is
|
|
86
|
-
the generated-UI equivalent of exporting a component plus its prop types.
|
|
87
|
-
|
|
88
|
-
The catalog tells the agent:
|
|
89
|
-
|
|
90
|
-
- Component names, such as `Text`, `Column`, `ProductTile`.
|
|
91
|
-
- Prop names and types.
|
|
92
|
-
- Required fields.
|
|
93
|
-
- Allowed enum values.
|
|
94
|
-
- Optional functions for dynamic formatting and validation.
|
|
95
|
-
|
|
96
|
-
The catalog tells the client:
|
|
24
|
+
## Install
|
|
97
25
|
|
|
98
|
-
|
|
99
|
-
- Which component names are safe to render.
|
|
100
|
-
|
|
101
|
-
### Agent
|
|
102
|
-
|
|
103
|
-
The agent is a UI planner. It receives normal chat messages, reads the catalog,
|
|
104
|
-
and returns A2UI JSON messages. Your backend should validate those messages
|
|
105
|
-
before returning them to the client.
|
|
106
|
-
|
|
107
|
-
The important product rule is: the agent designs within your catalog. If a
|
|
108
|
-
component is not in the catalog, it should not appear in the generated UI.
|
|
109
|
-
|
|
110
|
-
### Client
|
|
111
|
-
|
|
112
|
-
The client owns transport and rendering. It fetches messages from the agent,
|
|
113
|
-
pushes them into `MessageStore`, renders `<A2UI>`, and forwards generated user
|
|
114
|
-
actions back to your backend.
|
|
115
|
-
|
|
116
|
-
If you know `useSyncExternalStore`, the `MessageStore` idea should feel
|
|
117
|
-
familiar: it is an append-only external store of protocol messages. `<A2UI>`
|
|
118
|
-
subscribes to it and updates the rendered surface as messages arrive.
|
|
119
|
-
|
|
120
|
-
## Quickstart
|
|
121
|
-
|
|
122
|
-
In your ReactLynx app, install the GenUI package. The CLI requires Node.js 22
|
|
123
|
-
or newer and is exposed by the same package.
|
|
26
|
+
Install the published GenUI package in a ReactLynx app:
|
|
124
27
|
|
|
125
28
|
```sh
|
|
126
|
-
pnpm add @lynx-js/genui
|
|
127
|
-
genui a2ui --help
|
|
29
|
+
pnpm add @lynx-js/genui @lynx-js/react
|
|
128
30
|
```
|
|
129
31
|
|
|
130
|
-
|
|
131
|
-
|
|
132
|
-
and render validated A2UI messages in your ReactLynx client.
|
|
133
|
-
|
|
134
|
-
### 1. Catalog: Turn React Components Into Agent-Visible Components
|
|
32
|
+
Import the optional default theme tokens once if you want to use the built-in
|
|
33
|
+
light/dark CSS variables:
|
|
135
34
|
|
|
136
|
-
|
|
137
|
-
|
|
138
|
-
|
|
139
|
-
```tsx
|
|
140
|
-
/**
|
|
141
|
-
* Product tile for commerce recommendations.
|
|
142
|
-
*
|
|
143
|
-
* @a2uiCatalog ProductTile
|
|
144
|
-
*/
|
|
145
|
-
export interface ProductTileProps {
|
|
146
|
-
/** Product name shown as the title. */
|
|
147
|
-
title: string;
|
|
148
|
-
/** Price text already localized by the caller. */
|
|
149
|
-
price: string;
|
|
150
|
-
/** Image search query or resolved URL. */
|
|
151
|
-
imageUrl?: string;
|
|
152
|
-
}
|
|
153
|
-
|
|
154
|
-
export function ProductTile(props: ProductTileProps) {
|
|
155
|
-
return (
|
|
156
|
-
<view className='product-tile'>
|
|
157
|
-
{props.imageUrl ? <image src={props.imageUrl} /> : null}
|
|
158
|
-
<text>{props.title}</text>
|
|
159
|
-
<text>{props.price}</text>
|
|
160
|
-
</view>
|
|
161
|
-
);
|
|
162
|
-
}
|
|
163
|
-
|
|
164
|
-
ProductTile.displayName = 'ProductTile';
|
|
165
|
-
```
|
|
166
|
-
|
|
167
|
-
Generate a schema for the agent:
|
|
168
|
-
|
|
169
|
-
```sh
|
|
170
|
-
genui a2ui generate catalog --catalog-dir src/catalog --out-dir dist/catalog
|
|
171
|
-
```
|
|
172
|
-
|
|
173
|
-
Then pair the component with its manifest:
|
|
174
|
-
|
|
175
|
-
```tsx
|
|
176
|
-
import {
|
|
177
|
-
Button,
|
|
178
|
-
Column,
|
|
179
|
-
Text,
|
|
180
|
-
createMessageStore,
|
|
181
|
-
defineCatalog,
|
|
182
|
-
serializeCatalog,
|
|
183
|
-
} from '@lynx-js/genui/a2ui';
|
|
184
|
-
import buttonManifest from '@lynx-js/genui/a2ui/catalog/Button/catalog.json'
|
|
185
|
-
with { type: 'json' };
|
|
186
|
-
import columnManifest from '@lynx-js/genui/a2ui/catalog/Column/catalog.json'
|
|
187
|
-
with { type: 'json' };
|
|
188
|
-
import textManifest from '@lynx-js/genui/a2ui/catalog/Text/catalog.json'
|
|
189
|
-
with { type: 'json' };
|
|
190
|
-
import productTileManifest from './dist/catalog/ProductTile/catalog.json'
|
|
191
|
-
with { type: 'json' };
|
|
192
|
-
|
|
193
|
-
export const uiCatalog = defineCatalog([
|
|
194
|
-
[Text, textManifest],
|
|
195
|
-
[Column, columnManifest],
|
|
196
|
-
[Button, buttonManifest],
|
|
197
|
-
[ProductTile, productTileManifest],
|
|
198
|
-
]);
|
|
199
|
-
|
|
200
|
-
export const catalogHandshake = serializeCatalog(uiCatalog);
|
|
201
|
-
export const store = createMessageStore();
|
|
202
|
-
```
|
|
203
|
-
|
|
204
|
-
Use `catalogHandshake` when your own transport or agent consumes the client
|
|
205
|
-
handshake format. If your agent uses a different internal catalog format to
|
|
206
|
-
build prompts, add an explicit backend conversion step so the agent sees the
|
|
207
|
-
same component names that the client has registered.
|
|
208
|
-
|
|
209
|
-
There is intentionally no exported "all built-ins" constant. Importing every
|
|
210
|
-
component makes bundle cost invisible and weakens tree-shaking. Import only the
|
|
211
|
-
built-in components and catalog manifests your generated UI should be allowed to
|
|
212
|
-
use.
|
|
213
|
-
|
|
214
|
-
Production note: minifiers can rewrite function names. Set
|
|
215
|
-
`ProductTile.displayName = 'ProductTile'` or pair custom components with their
|
|
216
|
-
manifest so the protocol name stays stable.
|
|
217
|
-
|
|
218
|
-
### 2. CLI: Generate Catalogs And Prompts
|
|
219
|
-
|
|
220
|
-
The CLI is the build-time bridge between React source code and the agent. Use
|
|
221
|
-
it when you want repeatable artifacts instead of hand-maintained JSON:
|
|
222
|
-
|
|
223
|
-
- `generate catalog` reads TypeScript catalog contracts and writes
|
|
224
|
-
`dist/catalog/<Component>/catalog.json`.
|
|
225
|
-
- `generate prompt` reads generated catalog artifacts and writes an A2UI system
|
|
226
|
-
prompt for an agent.
|
|
227
|
-
|
|
228
|
-
Run the published CLI package through `npx`:
|
|
229
|
-
|
|
230
|
-
```sh
|
|
231
|
-
genui a2ui generate catalog \
|
|
232
|
-
--catalog-dir src/catalog \
|
|
233
|
-
--source src/functions \
|
|
234
|
-
--out-dir dist/catalog
|
|
235
|
-
|
|
236
|
-
genui a2ui generate prompt \
|
|
237
|
-
--catalog-dir dist/catalog \
|
|
238
|
-
--catalog-id https://example.com/catalogs/custom/v1/catalog.json \
|
|
239
|
-
--out dist/a2ui-system-prompt.txt
|
|
240
|
-
```
|
|
241
|
-
|
|
242
|
-
When your build already produces TypeDoc JSON, keep the same
|
|
243
|
-
`genui a2ui` command prefix and pass that file to `generate catalog`:
|
|
244
|
-
|
|
245
|
-
```sh
|
|
246
|
-
genui a2ui generate catalog \
|
|
247
|
-
--typedoc-json typedoc.json \
|
|
248
|
-
--out-dir dist/catalog
|
|
249
|
-
```
|
|
250
|
-
|
|
251
|
-
Key options:
|
|
252
|
-
|
|
253
|
-
| Option | Use |
|
|
254
|
-
| ----------------------- | ---------------------------------------------------------------------------- |
|
|
255
|
-
| `--catalog-dir <dir>` | Scan catalog component interfaces, or read generated artifacts for prompts. |
|
|
256
|
-
| `--source <path>` | Add source files or directories, commonly for catalog functions. Repeatable. |
|
|
257
|
-
| `--typedoc-json <file>` | Reuse an existing TypeDoc JSON project instead of running TypeDoc. |
|
|
258
|
-
| `--out-dir <dir>` | Write generated catalog artifacts. Defaults to `dist/catalog`. |
|
|
259
|
-
| `--catalog-id <id>` | Set the catalog id expected in generated `createSurface` messages. |
|
|
260
|
-
| `--out <file>` | Write the generated prompt to a file instead of stdout. |
|
|
261
|
-
| `--appendix <text>` | Add extra agent instructions to the generated prompt. |
|
|
262
|
-
|
|
263
|
-
Catalog authoring details:
|
|
264
|
-
|
|
265
|
-
- Put `@a2uiCatalog` on the props `interface`, not on the component function.
|
|
266
|
-
You can pass the component name explicitly, such as `@a2uiCatalog ProductTile`.
|
|
267
|
-
If the tag is empty, the generator infers the name by removing a trailing
|
|
268
|
-
`Props` or `ComponentProps` from the interface name.
|
|
269
|
-
- TypeDoc comments become schema metadata: summary text and `@remarks` become
|
|
270
|
-
`description`, `@defaultValue` or `@default` become `default`,
|
|
271
|
-
`@deprecated` becomes `deprecated: true`, and optional properties are omitted
|
|
272
|
-
from `required`. For object or array defaults, put the JSON value inside a
|
|
273
|
-
code span, such as `` @defaultValue `{}` ``.
|
|
274
|
-
- Supported prop types include `string`, `number`, `boolean`, string literal
|
|
275
|
-
enums, unions, arrays, inline object types, and `Record<string, T>`.
|
|
276
|
-
- Avoid `any`, `unknown`, `null`, `undefined`, `never`, `void`, nullable
|
|
277
|
-
unions, most imported aliases, referenced external interfaces, and
|
|
278
|
-
non-string `Record` keys. Inline the agent-facing fields directly in the
|
|
279
|
-
marked interface.
|
|
280
|
-
- The scanner accepts `.ts`, `.tsx`, `.js`, `.jsx`, `.mts`, and `.cts` files.
|
|
281
|
-
It ignores `.d.ts`, `node_modules`, `dist`, and `.turbo`.
|
|
282
|
-
|
|
283
|
-
Operational notes:
|
|
284
|
-
|
|
285
|
-
- Keep generated catalog artifacts in your package build output and commit API
|
|
286
|
-
reports or generated manifests when the package contract expects them.
|
|
287
|
-
- Regenerate catalog artifacts whenever a catalog-facing props interface or
|
|
288
|
-
`@a2uiFunction` definition changes.
|
|
289
|
-
- `generate prompt` uses the built-in A2UI basic catalog when `--catalog-dir`
|
|
290
|
-
is omitted; pass `--catalog-dir` for custom generated catalogs.
|
|
291
|
-
- The generated prompt and the client catalog must describe the same component
|
|
292
|
-
names and props. A mismatch can pass server validation but render as
|
|
293
|
-
unsupported on the client.
|
|
294
|
-
- `functions` and `theme` are not inferred from component props. Add them
|
|
295
|
-
explicitly through generated function definitions or prompt/catalog helpers.
|
|
296
|
-
|
|
297
|
-
Keep the generated prompt with your backend code and the generated catalog
|
|
298
|
-
artifacts with your app package so agent and client deployments stay in sync.
|
|
299
|
-
|
|
300
|
-
### 3. Agent: Ask For UI, Receive Validated Messages
|
|
301
|
-
|
|
302
|
-
Your agent service is a backend route in your product, not browser code. It
|
|
303
|
-
should:
|
|
304
|
-
|
|
305
|
-
- Load the A2UI system prompt generated by `genui a2ui generate
|
|
306
|
-
prompt`.
|
|
307
|
-
- Add conversation history, user intent, and any product state the model needs.
|
|
308
|
-
- Call your model provider from the server.
|
|
309
|
-
- Validate or repair model output before returning A2UI messages to the client.
|
|
310
|
-
- Keep provider credentials, base URLs, and model selection out of untrusted
|
|
311
|
-
browser requests.
|
|
312
|
-
|
|
313
|
-
A typical request shape looks like this:
|
|
314
|
-
|
|
315
|
-
```sh
|
|
316
|
-
curl https://your-domain.example/api/a2ui/chat \
|
|
317
|
-
-H 'Content-Type: application/json' \
|
|
318
|
-
-d '{
|
|
319
|
-
"messages": [
|
|
320
|
-
{
|
|
321
|
-
"role": "user",
|
|
322
|
-
"content": "Create a compact weather card with a photo, temperature, humidity, and a Refresh button."
|
|
323
|
-
}
|
|
324
|
-
]
|
|
325
|
-
}'
|
|
326
|
-
```
|
|
327
|
-
|
|
328
|
-
The response contains `messages`. Those are not React elements. They are data
|
|
329
|
-
instructions that the client renderer can process.
|
|
330
|
-
|
|
331
|
-
A tiny A2UI response looks like this:
|
|
332
|
-
|
|
333
|
-
```json
|
|
334
|
-
[
|
|
335
|
-
{
|
|
336
|
-
"version": "v0.9",
|
|
337
|
-
"createSurface": {
|
|
338
|
-
"surfaceId": "main",
|
|
339
|
-
"catalogId": "https://a2ui.org/specification/v0_9/basic_catalog.json"
|
|
340
|
-
}
|
|
341
|
-
},
|
|
342
|
-
{
|
|
343
|
-
"version": "v0.9",
|
|
344
|
-
"updateComponents": {
|
|
345
|
-
"surfaceId": "main",
|
|
346
|
-
"components": [
|
|
347
|
-
{
|
|
348
|
-
"id": "root",
|
|
349
|
-
"component": "Column",
|
|
350
|
-
"children": ["title"]
|
|
351
|
-
},
|
|
352
|
-
{
|
|
353
|
-
"id": "title",
|
|
354
|
-
"component": "Text",
|
|
355
|
-
"text": "Hello from generated UI"
|
|
356
|
-
}
|
|
357
|
-
]
|
|
358
|
-
}
|
|
359
|
-
}
|
|
360
|
-
]
|
|
35
|
+
```ts
|
|
36
|
+
import '@lynx-js/genui/a2ui/styles/theme.css';
|
|
361
37
|
```
|
|
362
38
|
|
|
363
|
-
|
|
364
|
-
useful to recognize the structure when debugging.
|
|
365
|
-
|
|
366
|
-
Important endpoints:
|
|
367
|
-
|
|
368
|
-
| Endpoint | Use |
|
|
369
|
-
| ------------------------------ | ----------------------------------------------------------------------------------- |
|
|
370
|
-
| `GET /api/a2ui/health` | Optional health/configuration check for your backend. |
|
|
371
|
-
| `POST /api/a2ui/chat` | Return one validated JSON response. |
|
|
372
|
-
| `POST /api/a2ui/stream` | Stream model deltas as SSE, then emit validated messages in the final `done` event. |
|
|
373
|
-
| `POST /api/a2ui/action` | Convert a client action into the next validated A2UI response. |
|
|
374
|
-
| `POST /api/a2ui/action/stream` | Stream an action response and final validation payload. |
|
|
375
|
-
|
|
376
|
-
Common server-side configuration:
|
|
377
|
-
|
|
378
|
-
| Variable | Purpose |
|
|
379
|
-
| ------------------------- | ------------------------------------------------------------------------ |
|
|
380
|
-
| `OPENAI_API_KEY` | Model credential kept on the server. |
|
|
381
|
-
| `OPENAI_MODEL` | Model id chosen by your backend. |
|
|
382
|
-
| `OPENAI_BASE_URL` | Optional OpenAI-compatible endpoint. |
|
|
383
|
-
| `OPENAI_API_STYLE` | `responses` or `chat`, depending on your provider integration. |
|
|
384
|
-
| `IMAGE_PROVIDER_API_KEY` | Optional image provider credential if your agent resolves image queries. |
|
|
385
|
-
| `A2UI_CORS_ORIGINS` | Comma-separated browser origins allowed by your server. |
|
|
386
|
-
| `A2UI_RATE_LIMIT_PER_MIN` | Per-client request limit for your server. |
|
|
387
|
-
|
|
388
|
-
### 4. Client: Render Messages Like React State
|
|
39
|
+
## Quick Start
|
|
389
40
|
|
|
390
|
-
|
|
391
|
-
|
|
392
|
-
components.
|
|
41
|
+
Create a `MessageStore`, register the components that generated UI is allowed
|
|
42
|
+
to render, and forward generated actions back to your Agent service.
|
|
393
43
|
|
|
394
44
|
```tsx
|
|
395
45
|
import {
|
|
396
46
|
A2UI,
|
|
397
47
|
Button,
|
|
398
|
-
Column,
|
|
399
48
|
Text,
|
|
49
|
+
basicFunctions,
|
|
400
50
|
createMessageStore,
|
|
51
|
+
normalizePayloadToMessages,
|
|
401
52
|
} from '@lynx-js/genui/a2ui';
|
|
402
|
-
import type { UserActionPayload } from '@lynx-js/genui/a2ui';
|
|
403
53
|
|
|
404
54
|
const store = createMessageStore();
|
|
405
|
-
const catalogs = [Text,
|
|
55
|
+
const catalogs = [Text, Button, ...basicFunctions];
|
|
406
56
|
|
|
407
|
-
async function sendPrompt(
|
|
408
|
-
const
|
|
57
|
+
async function sendPrompt(input: string) {
|
|
58
|
+
const res = await fetch('/a2ui/chat', {
|
|
409
59
|
method: 'POST',
|
|
410
60
|
headers: { 'Content-Type': 'application/json' },
|
|
411
|
-
body: JSON.stringify({
|
|
412
|
-
messages: [{ role: 'user', content }],
|
|
413
|
-
}),
|
|
61
|
+
body: JSON.stringify({ messages: [{ role: 'user', content: input }] }),
|
|
414
62
|
});
|
|
415
|
-
const body = await response.json();
|
|
416
|
-
for (const message of body.messages ?? []) {
|
|
417
|
-
store.push(message);
|
|
418
|
-
}
|
|
419
|
-
}
|
|
420
|
-
|
|
421
|
-
async function sendAction(action: UserActionPayload) {
|
|
422
|
-
const response = await fetch('/api/a2ui/action', {
|
|
423
|
-
method: 'POST',
|
|
424
|
-
headers: { 'Content-Type': 'application/json' },
|
|
425
|
-
body: JSON.stringify({
|
|
426
|
-
surfaceId: action.surfaceId,
|
|
427
|
-
action,
|
|
428
|
-
}),
|
|
429
|
-
});
|
|
430
|
-
const body = await response.json();
|
|
431
|
-
for (const message of body.messages ?? []) {
|
|
432
|
-
store.push(message);
|
|
433
|
-
}
|
|
434
|
-
}
|
|
435
|
-
|
|
436
|
-
export function GeneratedUIScreen(): import('@lynx-js/react').ReactNode {
|
|
437
|
-
return (
|
|
438
|
-
<A2UI
|
|
439
|
-
messageStore={store}
|
|
440
|
-
catalogs={catalogs}
|
|
441
|
-
onAction={(action) => {
|
|
442
|
-
void sendAction(action);
|
|
443
|
-
}}
|
|
444
|
-
wrapSurface={(children) => <view className='a2ui-light'>{children}</view>}
|
|
445
|
-
/>
|
|
446
|
-
);
|
|
447
|
-
}
|
|
448
|
-
```
|
|
449
|
-
|
|
450
|
-
Map this back to React:
|
|
451
|
-
|
|
452
|
-
- `MessageStore` is the external state source.
|
|
453
|
-
- `store.push(message)` is like receiving the next state update from the
|
|
454
|
-
server.
|
|
455
|
-
- `catalogs` is the allowlist of components the generated tree may use.
|
|
456
|
-
- `onAction` is like an event handler, except the event is serialized and sent
|
|
457
|
-
back to the agent.
|
|
458
|
-
- Passing a new React `key` to `<A2UI>` starts a fresh renderer session.
|
|
459
|
-
|
|
460
|
-
## Transport Layer
|
|
461
|
-
|
|
462
|
-
GenUI does not prescribe one transport. The protocol messages can travel over
|
|
463
|
-
REST, SSE, WebSocket, A2A, AG UI, MCP, or an in-process mock. In a React app,
|
|
464
|
-
the transport layer is the adapter between your product state and
|
|
465
|
-
`MessageStore`.
|
|
466
|
-
|
|
467
|
-
It owns:
|
|
468
|
-
|
|
469
|
-
- Calling the agent endpoint.
|
|
470
|
-
- Passing conversation history and data-model snapshots.
|
|
471
|
-
- Parsing JSON or streaming SSE responses.
|
|
472
|
-
- Pushing validated A2UI messages into the store in order.
|
|
473
|
-
- Forwarding `onAction` payloads back to the agent.
|
|
474
|
-
- Cancelling stale requests and surfacing errors.
|
|
475
|
-
|
|
476
|
-
It should not own:
|
|
477
|
-
|
|
478
|
-
- Rendering A2UI components directly.
|
|
479
|
-
- Mutating the generated component tree by hand.
|
|
480
|
-
- Trusting arbitrary prose from the model as UI.
|
|
481
|
-
- Letting browser clients override provider credentials in production.
|
|
482
|
-
|
|
483
|
-
### Interface Best Practices
|
|
484
|
-
|
|
485
|
-
Keep the transport small and explicit:
|
|
486
|
-
|
|
487
|
-
```ts
|
|
488
|
-
import type { MessageStore, UserActionPayload } from '@lynx-js/genui/a2ui';
|
|
489
|
-
|
|
490
|
-
interface ConversationContext {
|
|
491
|
-
history: Array<{ role: 'user' | 'assistant' | 'system'; content: string }>;
|
|
492
|
-
dataModel: Record<string, unknown>;
|
|
493
|
-
}
|
|
494
|
-
|
|
495
|
-
interface A2UITransport {
|
|
496
|
-
generate(input: {
|
|
497
|
-
prompt: string;
|
|
498
|
-
conversation?: ConversationContext;
|
|
499
|
-
signal?: AbortSignal;
|
|
500
|
-
}): Promise<unknown[]>;
|
|
501
|
-
respondToAction(input: {
|
|
502
|
-
surfaceId: string;
|
|
503
|
-
action: UserActionPayload;
|
|
504
|
-
conversation?: ConversationContext;
|
|
505
|
-
signal?: AbortSignal;
|
|
506
|
-
}): Promise<unknown[]>;
|
|
507
|
-
}
|
|
508
|
-
|
|
509
|
-
async function applyMessages(
|
|
510
|
-
store: MessageStore,
|
|
511
|
-
messages: unknown[],
|
|
512
|
-
): Promise<void> {
|
|
513
|
-
for (const message of messages) {
|
|
514
|
-
store.push(message);
|
|
515
|
-
}
|
|
516
|
-
}
|
|
517
|
-
```
|
|
518
|
-
|
|
519
|
-
This keeps generated UI as data until the last step. The renderer remains the
|
|
520
|
-
only place that interprets A2UI messages.
|
|
521
|
-
|
|
522
|
-
### REST Baseline
|
|
523
|
-
|
|
524
|
-
Use routes such as `/api/a2ui/chat` and `/api/a2ui/action` when you want a
|
|
525
|
-
simple request/response implementation:
|
|
526
|
-
|
|
527
|
-
```ts
|
|
528
|
-
function extractMessages(payload: unknown): unknown[] {
|
|
529
|
-
if (Array.isArray(payload)) return payload;
|
|
530
|
-
if (typeof payload === 'string') {
|
|
531
|
-
try {
|
|
532
|
-
return extractMessages(JSON.parse(payload));
|
|
533
|
-
} catch {
|
|
534
|
-
return [];
|
|
535
|
-
}
|
|
536
|
-
}
|
|
537
|
-
if (!payload || typeof payload !== 'object') return [];
|
|
538
|
-
|
|
539
|
-
const record = payload as {
|
|
540
|
-
messages?: unknown;
|
|
541
|
-
validation?: { messages?: unknown };
|
|
542
|
-
text?: unknown;
|
|
543
|
-
};
|
|
544
|
-
if (Array.isArray(record.messages)) return record.messages;
|
|
545
|
-
if (Array.isArray(record.validation?.messages)) {
|
|
546
|
-
return record.validation.messages;
|
|
547
|
-
}
|
|
548
|
-
if (typeof record.text === 'string') return extractMessages(record.text);
|
|
549
|
-
return [];
|
|
550
|
-
}
|
|
551
|
-
|
|
552
|
-
async function postA2UI(
|
|
553
|
-
url: string,
|
|
554
|
-
body: unknown,
|
|
555
|
-
signal?: AbortSignal,
|
|
556
|
-
): Promise<unknown[]> {
|
|
557
|
-
const response = await fetch(url, {
|
|
558
|
-
method: 'POST',
|
|
559
|
-
headers: { 'Content-Type': 'application/json' },
|
|
560
|
-
body: JSON.stringify(body),
|
|
561
|
-
signal,
|
|
562
|
-
});
|
|
563
|
-
|
|
564
|
-
const payload = await response.json().catch(() => ({}));
|
|
565
|
-
if (!response.ok) {
|
|
566
|
-
throw new Error(`A2UI request failed: ${response.status}`);
|
|
567
|
-
}
|
|
568
|
-
|
|
569
|
-
const messages = extractMessages(payload);
|
|
570
|
-
if (messages.length === 0) {
|
|
571
|
-
throw new Error('A2UI response did not include renderable messages');
|
|
572
|
-
}
|
|
573
|
-
return messages;
|
|
574
|
-
}
|
|
575
|
-
```
|
|
576
|
-
|
|
577
|
-
Then wire it to the renderer:
|
|
578
|
-
|
|
579
|
-
```ts
|
|
580
|
-
async function generate(prompt: string, signal?: AbortSignal) {
|
|
581
|
-
const messages = await postA2UI(
|
|
582
|
-
'/api/a2ui/chat',
|
|
583
|
-
{ messages: [{ role: 'user', content: prompt }] },
|
|
584
|
-
signal,
|
|
585
|
-
);
|
|
586
|
-
await applyMessages(store, messages);
|
|
587
|
-
}
|
|
588
|
-
|
|
589
|
-
async function respondToAction(
|
|
590
|
-
action: UserActionPayload,
|
|
591
|
-
signal?: AbortSignal,
|
|
592
|
-
) {
|
|
593
|
-
const messages = await postA2UI(
|
|
594
|
-
'/api/a2ui/action',
|
|
595
|
-
{ surfaceId: action.surfaceId, action },
|
|
596
|
-
signal,
|
|
597
|
-
);
|
|
598
|
-
await applyMessages(store, messages);
|
|
599
|
-
}
|
|
600
|
-
```
|
|
601
|
-
|
|
602
|
-
### SSE Streaming
|
|
603
|
-
|
|
604
|
-
Use routes such as `/api/a2ui/stream` and `/api/a2ui/action/stream` when you
|
|
605
|
-
want to show generation progress. The server emits:
|
|
606
|
-
|
|
607
|
-
- `delta`: raw model text, useful for an inspector or loading state.
|
|
608
|
-
- `repair`: optional metadata when the server had to repair invalid model
|
|
609
|
-
output.
|
|
610
|
-
- `done`: the final validated payload. Use the messages from this event for
|
|
611
|
-
rendering.
|
|
612
|
-
- `error`: structured failure payload.
|
|
613
|
-
|
|
614
|
-
```ts
|
|
615
|
-
interface SseFrame {
|
|
616
|
-
event: string;
|
|
617
|
-
data: unknown;
|
|
618
|
-
}
|
|
619
|
-
|
|
620
|
-
function parseSseFrame(frame: string): SseFrame | null {
|
|
621
|
-
const lines = frame.split(/\r?\n/u);
|
|
622
|
-
let event = 'message';
|
|
623
|
-
const dataLines: string[] = [];
|
|
624
|
-
|
|
625
|
-
for (const line of lines) {
|
|
626
|
-
if (line.startsWith('event:')) {
|
|
627
|
-
event = line.slice('event:'.length).trim();
|
|
628
|
-
} else if (line.startsWith('data:')) {
|
|
629
|
-
dataLines.push(line.slice('data:'.length).trimStart());
|
|
630
|
-
}
|
|
631
|
-
}
|
|
632
|
-
|
|
633
|
-
if (dataLines.length === 0) return null;
|
|
634
|
-
const raw = dataLines.join('\n');
|
|
635
|
-
try {
|
|
636
|
-
return { event, data: JSON.parse(raw) };
|
|
637
|
-
} catch {
|
|
638
|
-
return { event, data: raw };
|
|
639
|
-
}
|
|
640
|
-
}
|
|
641
|
-
|
|
642
|
-
async function readA2UISse(
|
|
643
|
-
response: Response,
|
|
644
|
-
onDelta?: (text: string) => void,
|
|
645
|
-
): Promise<unknown[]> {
|
|
646
|
-
const reader = response.body?.getReader();
|
|
647
|
-
if (!reader) return [];
|
|
648
|
-
|
|
649
|
-
const decoder = new TextDecoder();
|
|
650
|
-
let buffer = '';
|
|
651
|
-
let generatedText = '';
|
|
652
|
-
|
|
653
|
-
while (true) {
|
|
654
|
-
const { done, value } = await reader.read();
|
|
655
|
-
buffer += decoder.decode(value, { stream: !done });
|
|
656
|
-
|
|
657
|
-
const frames = buffer.split(/\r?\n\r?\n/u);
|
|
658
|
-
buffer = frames.pop() ?? '';
|
|
659
|
-
|
|
660
|
-
for (const frame of frames) {
|
|
661
|
-
const parsed = parseSseFrame(frame);
|
|
662
|
-
if (!parsed) continue;
|
|
663
|
-
|
|
664
|
-
if (parsed.event === 'delta') {
|
|
665
|
-
const text = (parsed.data as { text?: unknown }).text;
|
|
666
|
-
if (typeof text === 'string') {
|
|
667
|
-
generatedText += text;
|
|
668
|
-
onDelta?.(generatedText);
|
|
669
|
-
}
|
|
670
|
-
continue;
|
|
671
|
-
}
|
|
672
|
-
|
|
673
|
-
if (parsed.event === 'done') {
|
|
674
|
-
const messages = extractMessages(parsed.data);
|
|
675
|
-
if (messages.length === 0) {
|
|
676
|
-
throw new Error('A2UI stream finished without renderable messages');
|
|
677
|
-
}
|
|
678
|
-
return messages;
|
|
679
|
-
}
|
|
680
|
-
|
|
681
|
-
if (parsed.event === 'error') {
|
|
682
|
-
throw new Error(JSON.stringify(parsed.data));
|
|
683
|
-
}
|
|
684
|
-
}
|
|
685
|
-
|
|
686
|
-
if (done) break;
|
|
687
|
-
}
|
|
688
63
|
|
|
689
|
-
|
|
690
|
-
}
|
|
64
|
+
store.push(normalizePayloadToMessages(await res.json()));
|
|
65
|
+
}
|
|
66
|
+
|
|
67
|
+
<A2UI
|
|
68
|
+
messageStore={store}
|
|
69
|
+
catalogs={catalogs}
|
|
70
|
+
wrapSurface={(children) => <view className='a2ui-light'>{children}</view>}
|
|
71
|
+
onAction={(action) => {
|
|
72
|
+
void fetch('/a2ui/action', {
|
|
73
|
+
method: 'POST',
|
|
74
|
+
headers: { 'Content-Type': 'application/json' },
|
|
75
|
+
body: JSON.stringify(action),
|
|
76
|
+
})
|
|
77
|
+
.then((res) => res.json())
|
|
78
|
+
.then((payload) => store.push(normalizePayloadToMessages(payload)));
|
|
79
|
+
}}
|
|
80
|
+
/>;
|
|
691
81
|
```
|
|
692
82
|
|
|
693
|
-
|
|
694
|
-
|
|
695
|
-
|
|
696
|
-
|
|
697
|
-
|
|
698
|
-
|
|
699
|
-
|
|
700
|
-
|
|
701
|
-
|
|
702
|
-
-
|
|
703
|
-
|
|
704
|
-
|
|
705
|
-
|
|
706
|
-
|
|
707
|
-
|
|
708
|
-
-
|
|
709
|
-
|
|
710
|
-
|
|
711
|
-
-
|
|
712
|
-
|
|
713
|
-
|
|
714
|
-
|
|
715
|
-
|
|
716
|
-
|
|
717
|
-
|
|
718
|
-
|
|
719
|
-
|
|
720
|
-
-
|
|
721
|
-
|
|
722
|
-
-
|
|
723
|
-
|
|
724
|
-
- Configure CORS and rate limits on the server before exposing the agent to
|
|
725
|
-
browsers.
|
|
726
|
-
- Version your catalog contract. The agent catalog and client catalog must
|
|
727
|
-
agree on component names and props, or validated output may still render as
|
|
728
|
-
unsupported on the client.
|
|
729
|
-
- Use deterministic mocks for tests. A transport can be an in-process async
|
|
730
|
-
generator that pushes known A2UI messages into the store.
|
|
731
|
-
|
|
732
|
-
Common mistakes:
|
|
733
|
-
|
|
734
|
-
- Rendering raw model prose instead of validated A2UI messages.
|
|
735
|
-
- Reusing one `MessageStore` for unrelated conversations without remounting
|
|
736
|
-
`<A2UI key={...}>`.
|
|
737
|
-
- Dropping `conversation.dataModel`, which makes follow-up actions lose state.
|
|
738
|
-
- Retrying non-idempotent actions automatically, which can apply the same user
|
|
739
|
-
intent twice.
|
|
740
|
-
- Allowing generated image URLs, remote endpoints, or provider overrides from
|
|
741
|
-
untrusted browser input.
|
|
742
|
-
|
|
743
|
-
## Try The Playground
|
|
744
|
-
|
|
745
|
-
The hosted playground is the fastest way to see the whole loop before
|
|
746
|
-
integrating it into an app:
|
|
747
|
-
|
|
748
|
-
[https://lynx-stack.dev/a2ui/](https://lynx-stack.dev/a2ui/)
|
|
749
|
-
|
|
750
|
-
Use the hosted page to try the demos, inspect generated A2UI JSON, browse the
|
|
751
|
-
catalog, and preview Lynx surfaces.
|
|
752
|
-
|
|
753
|
-
Use the playground to:
|
|
754
|
-
|
|
755
|
-
- Describe UI in natural language and inspect the generated A2UI JSON.
|
|
756
|
-
- Browse the component catalog like a React component library.
|
|
757
|
-
- Preview the generated Lynx surface.
|
|
758
|
-
- Test action flows such as submit, refresh, and selection.
|
|
759
|
-
- Generate preview URLs and QR codes for native Lynx testing.
|
|
760
|
-
|
|
761
|
-
## Glossary For React Developers
|
|
762
|
-
|
|
763
|
-
| GenUI term | React-friendly meaning |
|
|
764
|
-
| ------------------ | -------------------------------------------------------------------------------------- |
|
|
765
|
-
| A2UI | JSON messages that describe UI changes. Similar to a serialized, constrained UI tree. |
|
|
766
|
-
| Surface | A generated UI root, similar to a mounted app region. |
|
|
767
|
-
| Catalog | The approved component library and prop schema exposed to the agent. |
|
|
768
|
-
| `MessageStore` | Append-only external store that receives protocol messages. |
|
|
769
|
-
| `updateComponents` | "Render these component instances with these props." |
|
|
770
|
-
| `updateDataModel` | "Patch the data used by bound props." Similar to remote state updates. |
|
|
771
|
-
| Action | A generated UI event, similar to `onClick`, sent back to the agent as structured data. |
|
|
772
|
-
|
|
773
|
-
## Protocol Notes
|
|
774
|
-
|
|
775
|
-
The current A2UI path targets A2UI v0.9.
|
|
776
|
-
|
|
777
|
-
- The model must output a raw JSON array, not Markdown.
|
|
778
|
-
- A fresh response starts with `createSurface`, followed by
|
|
779
|
-
`updateComponents` containing a `root` component.
|
|
780
|
-
- Components form a flat graph. Children are referenced by id rather than
|
|
781
|
-
inlined.
|
|
782
|
-
- Data bindings use JSON Pointer paths and must be populated by
|
|
783
|
-
`updateDataModel`.
|
|
784
|
-
- Interactive components emit action payloads. The client posts those actions
|
|
785
|
-
to the agent, and the agent returns update messages for the existing surface.
|
|
786
|
-
|
|
787
|
-
## Testing And Quality
|
|
788
|
-
|
|
789
|
-
Use your app's normal test runner. The high-value checks are:
|
|
790
|
-
|
|
791
|
-
- Unit-test catalog registration so every component name in generated messages
|
|
792
|
-
maps to the ReactLynx component you expect.
|
|
793
|
-
- Unit-test transport parsing with deterministic JSON and SSE fixtures,
|
|
794
|
-
including malformed responses and aborts.
|
|
795
|
-
- Replay saved A2UI message arrays through `<A2UI>` so renderer regressions are
|
|
796
|
-
visible without calling a model.
|
|
797
|
-
- E2E-test one prompt flow and one action flow with mocked agent responses
|
|
798
|
-
before adding model-backed tests.
|
|
799
|
-
|
|
800
|
-
## Product Direction
|
|
801
|
-
|
|
802
|
-
GenUI is designed around a few commitments:
|
|
803
|
-
|
|
804
|
-
- React remains the implementation layer. The agent chooses from components you
|
|
805
|
-
own.
|
|
806
|
-
- The catalog is the product contract. It keeps generated UI aligned with your
|
|
807
|
-
design system and platform constraints.
|
|
808
|
-
- Progressive rendering should make the UI useful before a turn fully
|
|
809
|
-
completes.
|
|
810
|
-
- Transports are replaceable. REST, SSE, WebSocket, A2A, AG UI, or MCP can all
|
|
811
|
-
carry the same A2UI messages.
|
|
812
|
-
- Generated UI should be inspectable, replayable, and judgeable in automated
|
|
813
|
-
workflows.
|
|
814
|
-
|
|
815
|
-
Start with the hosted Playground, then generate a small catalog in your app and
|
|
816
|
-
wire one prompt route plus one action route before expanding to richer
|
|
817
|
-
components.
|
|
83
|
+
`MessageStore` stores raw protocol messages in arrival order. `<A2UI>`
|
|
84
|
+
subscribes to it, processes new messages, renders the active surface, and emits
|
|
85
|
+
generated UI actions through `onAction`.
|
|
86
|
+
|
|
87
|
+
## What You Own
|
|
88
|
+
|
|
89
|
+
| Part | Owner | Role |
|
|
90
|
+
| ------------------------- | ---------------- | ------------------------------------------------------------------------------------------------------------------------ |
|
|
91
|
+
| `@lynx-js/genui/a2ui` | This package | ReactLynx renderer, `MessageStore`, catalog APIs, built-in components, protocol helpers, and client function entries. |
|
|
92
|
+
| `npx @lynx-js/genui a2ui` | GenUI CLI | Build-time commands for generating custom catalog artifacts and A2UI system prompts. |
|
|
93
|
+
| Your Agent service | Your application | Receives user prompts/actions, calls a model with the A2UI prompt and catalog, validates output, and returns messages. |
|
|
94
|
+
| Your transport adapter | Your application | Calls the Agent service, handles REST or streaming responses, writes messages into `MessageStore`, and forwards actions. |
|
|
95
|
+
|
|
96
|
+
## First Things To Know
|
|
97
|
+
|
|
98
|
+
- Pass only the components you want generated UI to use through `catalogs`.
|
|
99
|
+
- Include `...basicFunctions` when messages may use A2UI function calls such as
|
|
100
|
+
`formatString`, `formatDate`, `required`, `email`, or `and`.
|
|
101
|
+
- Pair components with their `catalog.json` manifests when you need
|
|
102
|
+
`serializeCatalog(...)` to send JSON schemas to an Agent.
|
|
103
|
+
- There is intentionally no `@lynx-js/genui/a2ui/catalog/all` export. Compose
|
|
104
|
+
the exact catalog you want at the integration site so bundle cost is visible.
|
|
105
|
+
- For a new turn or session, mount `<A2UI>` with a different `key`; the
|
|
106
|
+
component owns its `MessageProcessor` for the lifetime of the mount.
|
|
107
|
+
|
|
108
|
+
## More Docs
|
|
109
|
+
|
|
110
|
+
- [Overview and architecture](./docs/overview.md)
|
|
111
|
+
- [Catalogs, built-ins, and custom components](./docs/catalog-guide.md)
|
|
112
|
+
- [System prompts](./docs/system-prompts.md)
|
|
113
|
+
- [Open the A2UI playground](https://lynxjs.org/a2ui)
|