@wallarm-org/gen-ui 0.0.0-stage → 1.0.0

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.
Files changed (118) hide show
  1. package/README.md +153 -2
  2. package/dist/a2ui/A2uiSurfaces.d.ts +16 -0
  3. package/dist/a2ui/A2uiSurfaces.js +61 -0
  4. package/dist/a2ui/catalog.d.ts +16 -0
  5. package/dist/a2ui/catalog.js +66 -0
  6. package/dist/a2ui/check.d.ts +4 -0
  7. package/dist/a2ui/check.js +34 -0
  8. package/dist/a2ui/express.d.ts +38 -0
  9. package/dist/a2ui/express.js +64 -0
  10. package/dist/a2ui/index.d.ts +8 -0
  11. package/dist/a2ui/index.js +4 -0
  12. package/dist/a2ui/prompt.d.ts +16 -0
  13. package/dist/a2ui/prompt.js +17 -0
  14. package/dist/a2ui/toA2uiSchema.d.ts +19 -0
  15. package/dist/a2ui/toA2uiSchema.js +61 -0
  16. package/dist/components/Alert.d.ts +11 -0
  17. package/dist/components/Alert.js +43 -0
  18. package/dist/components/AreaChart.d.ts +36 -0
  19. package/dist/components/AreaChart.js +90 -0
  20. package/dist/components/Badge.d.ts +11 -0
  21. package/dist/components/Badge.js +30 -0
  22. package/dist/components/BarList.d.ts +26 -0
  23. package/dist/components/BarList.js +66 -0
  24. package/dist/components/Button.d.ts +10 -0
  25. package/dist/components/Button.js +40 -0
  26. package/dist/components/Card.d.ts +2 -0
  27. package/dist/components/Card.js +17 -0
  28. package/dist/components/ColumnChart.d.ts +32 -0
  29. package/dist/components/ColumnChart.js +114 -0
  30. package/dist/components/Grid.d.ts +29 -0
  31. package/dist/components/Grid.js +72 -0
  32. package/dist/components/Heading.d.ts +10 -0
  33. package/dist/components/Heading.js +29 -0
  34. package/dist/components/HorizontalBarStack.d.ts +39 -0
  35. package/dist/components/HorizontalBarStack.js +67 -0
  36. package/dist/components/LineChart.d.ts +36 -0
  37. package/dist/components/LineChart.js +101 -0
  38. package/dist/components/Metric.d.ts +35 -0
  39. package/dist/components/Metric.js +85 -0
  40. package/dist/components/PieChart.d.ts +25 -0
  41. package/dist/components/PieChart.js +83 -0
  42. package/dist/components/Section.d.ts +5 -0
  43. package/dist/components/Section.js +39 -0
  44. package/dist/components/Stack.d.ts +18 -0
  45. package/dist/components/Stack.js +41 -0
  46. package/dist/components/Table.d.ts +30 -0
  47. package/dist/components/Table.js +128 -0
  48. package/dist/components/Text.d.ts +9 -0
  49. package/dist/components/Text.js +27 -0
  50. package/dist/components/index.d.ts +20 -0
  51. package/dist/components/index.js +55 -0
  52. package/dist/core/check.d.ts +35 -0
  53. package/dist/core/check.js +63 -0
  54. package/dist/core/define.d.ts +64 -0
  55. package/dist/core/define.js +7 -0
  56. package/dist/core/format.d.ts +24 -0
  57. package/dist/core/format.js +74 -0
  58. package/dist/core/guards.d.ts +2 -0
  59. package/dist/core/guards.js +2 -0
  60. package/dist/fixtures/apiInventory.d.ts +36 -0
  61. package/dist/fixtures/apiInventory.js +287 -0
  62. package/dist/fixtures/attackSummary.d.ts +13 -0
  63. package/dist/fixtures/attackSummary.js +329 -0
  64. package/dist/fixtures/dashboard.d.ts +119 -0
  65. package/dist/fixtures/dashboard.js +428 -0
  66. package/dist/fixtures/incident.d.ts +44 -0
  67. package/dist/fixtures/incident.js +331 -0
  68. package/dist/fixtures/index.d.ts +13 -0
  69. package/dist/fixtures/index.js +9 -0
  70. package/dist/fixtures/liveMonitor.d.ts +9 -0
  71. package/dist/fixtures/liveMonitor.js +180 -0
  72. package/dist/fixtures/openui.d.ts +16 -0
  73. package/dist/fixtures/openui.js +74 -0
  74. package/dist/fixtures/scenarios.d.ts +13 -0
  75. package/dist/fixtures/scenarios.js +39 -0
  76. package/dist/fixtures/stream.d.ts +79 -0
  77. package/dist/fixtures/stream.js +147 -0
  78. package/dist/fixtures/weeklyReport.d.ts +46 -0
  79. package/dist/fixtures/weeklyReport.js +266 -0
  80. package/dist/index.d.ts +8 -0
  81. package/dist/index.js +4 -0
  82. package/dist/json-render/JsonRenderSurface.d.ts +13 -0
  83. package/dist/json-render/JsonRenderSurface.js +63 -0
  84. package/dist/json-render/catalog.d.ts +64 -0
  85. package/dist/json-render/catalog.js +50 -0
  86. package/dist/json-render/check.d.ts +4 -0
  87. package/dist/json-render/check.js +13 -0
  88. package/dist/json-render/index.d.ts +6 -0
  89. package/dist/json-render/index.js +4 -0
  90. package/dist/json-render/spec.d.ts +23 -0
  91. package/dist/json-render/spec.js +77 -0
  92. package/dist/openui/OpenUiSurface.d.ts +24 -0
  93. package/dist/openui/OpenUiSurface.js +30 -0
  94. package/dist/openui/catalog.d.ts +23 -0
  95. package/dist/openui/catalog.js +65 -0
  96. package/dist/openui/check.d.ts +4 -0
  97. package/dist/openui/check.js +68 -0
  98. package/dist/openui/index.d.ts +5 -0
  99. package/dist/openui/index.js +3 -0
  100. package/dist/playground/GenUiChatDemo.d.ts +11 -0
  101. package/dist/playground/GenUiChatDemo.js +148 -0
  102. package/dist/playground/GenUiPlayground.d.ts +15 -0
  103. package/dist/playground/GenUiPlayground.js +413 -0
  104. package/dist/playground/index.d.ts +3 -0
  105. package/dist/playground/index.js +2 -0
  106. package/dist/playground/useEmulatedAgent.d.ts +37 -0
  107. package/dist/playground/useEmulatedAgent.js +221 -0
  108. package/dist/render/ChartSkeleton.d.ts +6 -0
  109. package/dist/render/ChartSkeleton.js +26 -0
  110. package/dist/render/GuardedView.d.ts +18 -0
  111. package/dist/render/GuardedView.js +24 -0
  112. package/dist/render/Placeholder.d.ts +16 -0
  113. package/dist/render/Placeholder.js +15 -0
  114. package/dist/render/SurfaceBoundary.d.ts +15 -0
  115. package/dist/render/SurfaceBoundary.js +15 -0
  116. package/dist/render/warn.d.ts +1 -0
  117. package/dist/render/warn.js +2 -0
  118. package/package.json +94 -4
package/README.md CHANGED
@@ -1,3 +1,154 @@
1
- # Temporary Holding Version
1
+ # @wallarm-org/gen-ui
2
2
 
3
- This version is a temporary placeholder for this package. An operational version to replace this has been submitted for review and is awaiting a staged release.
3
+ Generative UI catalog for the Wallarm design system: an agent answers with design-system
4
+ components instead of text. Each component is defined once and rendered from
5
+ [A2UI](https://a2ui.org) v1.0 messages, [json-render](https://json-render.dev) specs or
6
+ [OpenUI](https://www.openui.com) Lang programs.
7
+
8
+ Experimental: published to npm as `@wallarm-org/gen-ui`, released from `main` with its own tags
9
+ (`gen-ui-v*`, see `RELEASE.md`). The user docs are on the site under
10
+ **AI → Generative UI** (`apps/website/docs/ai/generative-ui.mdx`).
11
+
12
+ ## Where things are
13
+
14
+ ```text
15
+ src/
16
+ ├── index.ts @wallarm-org/gen-ui: the catalog + the API to define entries
17
+ ├── core/ format-agnostic: no format library, React only as types
18
+ │ ├── check.ts checkNodes / describeIssues: the answer check every format shares
19
+ │ ├── define.ts GenUiComponent (an entry's contract), defineComponent,
20
+ │ │ subsetOf / allOf / Values (enums from DS types), seriesKey, unique
21
+ │ ├── format.ts named number and time formats (formatNumber, formatTime)
22
+ │ └── guards.ts isRecord: narrow untrusted agent output
23
+ ├── render/ React pieces every format shares
24
+ │ ├── GuardedView.tsx validates props against the schema, then View or Skeleton
25
+ │ ├── Placeholder.tsx what stands in for a part that isn't renderable yet
26
+ │ ├── ChartSkeleton.tsx the chart card skeleton the chart entries use
27
+ │ ├── SurfaceBoundary.tsx error boundary: a View that throws blanks its own surface only
28
+ │ └── warn.ts the one console.warn for rejected agent output
29
+ ├── components/ THE CATALOG: one file per entry, index.ts lists them in order
30
+ ├── a2ui/ @wallarm-org/gen-ui/a2ui
31
+ │ ├── catalog.tsx createA2uiCatalog: entries → A2UI Catalog (toJson() for the agent)
32
+ │ ├── A2uiSurfaces.tsx feeds messages to a MessageProcessor, renders its surfaces
33
+ │ ├── express.tsx /a2ui/express: A2UI Express, the compact text form, compiled by @a2ui/agent
34
+ │ ├── prompt.ts /a2ui/prompt: a2uiPrompt, the system prompt (Express or JSON) with the actions
35
+ │ ├── check.ts checkA2uiAnswer: the agent's mistakes in a finished answer
36
+ │ └── toA2uiSchema.ts zod 4 props → the zod 3 schema A2UI's binder reads
37
+ ├── json-render/ @wallarm-org/gen-ui/json-render
38
+ │ ├── catalog.tsx createJsonRenderCatalog: entries → catalog (prompt()) + registry
39
+ │ ├── check.ts checkJsonRenderAnswer: the same check for a spec
40
+ │ ├── spec.ts specFromLines (stream → spec, never throws), sanitizeSpec (allowlist)
41
+ │ └── JsonRenderSurface.tsx renders a spec; keeps its state in step as the app fills it
42
+ ├── openui/ @wallarm-org/gen-ui/openui
43
+ │ ├── catalog.tsx createOpenUiLibrary: entries → OpenUI library (prompt()), props in order
44
+ │ ├── check.ts checkOpenUiAnswer: the same check for a program
45
+ │ └── OpenUiSurface.tsx renders a program; the app's data arrives as $variables
46
+ ├── fixtures/ @wallarm-org/gen-ui/fixtures: recorded answers, every format
47
+ │ ├── stream.ts the scenario builder: layout chunks + app data → every stream (toStreams)
48
+ │ ├── scenarios.ts SCENARIOS: every answer a demo can replay, in picker order
49
+ │ ├── attackSummary.ts a chat answer written by hand in A2UI and json-render
50
+ │ ├── openui.ts a recorded A2UI answer as OpenUI Lang (toOpenUiLines, openUiAnswer)
51
+ │ ├── liveMonitor.ts the live example: the app refreshes the page every 2 s
52
+ │ └── dashboard.ts, … one scenario per file, built with stream.ts
53
+ ├── playground/ @wallarm-org/gen-ui/playground: the demos the playground app and the
54
+ │ │ docs site embed
55
+ │ ├── GenUiPlayground.tsx pick a prompt, replay its answer, read the stream beside it
56
+ │ ├── GenUiChatDemo.tsx answers inside the Wally chat, with an approval before Block
57
+ │ └── useEmulatedAgent.ts the scripted agent behind the chat demo
58
+ └── tests/ all tests (formats.test.tsx covers the shared behaviour)
59
+ ```
60
+
61
+ ## How a render works
62
+
63
+ ```text
64
+ components/Metric.tsx (one definition)
65
+ ├─ a2ui/catalog.tsx → A2UI catalog ─┐
66
+ └─ json-render/catalog.tsx → json-render ──┴→ agent output → GuardedView → View (DS components)
67
+ └ props don't pass yet → Skeleton
68
+ ```
69
+
70
+ 1. The app builds both catalogs once, at module scope, from `wallarmComponents`.
71
+ 2. The agent is prompted with the catalog: `toJson()` (A2UI) or `catalog.prompt()` (json-render).
72
+ 3. Its output streams in. `A2uiSurfaces` takes the messages so far; `JsonRenderSurface` takes
73
+ `specFromLines(lines)`.
74
+ 4. For every component, `GuardedView` checks the props against the entry's zod schema. Passing
75
+ props reach the `View`; failing ones (data not here yet, or a mistake) show the entry's
76
+ `Skeleton`. A child the agent hasn't sent yet shows a plain `Placeholder`.
77
+ 5. A pressed button fires an event; only actions in the app's allowlist reach `onAction`.
78
+
79
+ ## Add a component
80
+
81
+ 1. Create `src/components/<Name>.tsx` with `defineComponent({ name, description, props, View })`.
82
+ Copy the closest entry: `Badge.tsx` for a leaf, `Section.tsx` for a container, `BarList.tsx` for
83
+ a chart.
84
+ 2. Add it to `wallarmComponents` and the `export *` list in `src/components/index.ts`.
85
+ 3. Add a test in `src/tests/<Name>.test.tsx` that renders it through every format, with literal
86
+ and bound props, and with data that hasn't arrived (see `LineChart.test.tsx`).
87
+
88
+ An entry must follow these rules. Tests or typecheck enforce the ones marked ✓.
89
+
90
+ - Props are data: strings, numbers, booleans, enums, arrays, objects. No functions or ReactNode.
91
+ - No prop named `id`, `component`, `children` or `child` (A2UI puts props next to them).
92
+ - ✓ Every enum value comes from a DS type: `subsetOf<BadgeColor>()([...])` for some values,
93
+ `allOf<…>()([...])` for all of them, or a typed `Record<…, DSType>` map. No `as` casts in a
94
+ definition.
95
+ - Numbers and times take a named format from `core/format.ts`, never a formatter function.
96
+ - Events carry no arguments: `events: ['press']`, then `emit('press')`.
97
+ - A container sets `hasChildren: true` and renders `children`.
98
+ - The `View` gets props that already passed the schema, so it needn't guard against missing data;
99
+ declare a `Skeleton` shaped like the loaded component.
100
+ - The root of a widget fills its cell (`h-full`), so a Grid row lines up.
101
+ - The `description` is for the model: when to use it and the data shape, not how it looks.
102
+
103
+ ## Add a recorded answer
104
+
105
+ 1. Create `src/fixtures/<name>.ts` exporting a `Scenario` (see `dashboard.ts`): the prompt, the
106
+ agent's layout in the chunks it sends them (`node`, with every figure a `bind('/path')`), and
107
+ the app's data batches with the chunk each arrives after.
108
+ 2. Add `{ ...scenario, ...toStreams(scenario) }` to `SCENARIOS` in `src/fixtures/scenarios.ts`:
109
+ the playground and the docs page list it from there.
110
+ 3. Add a test that renders it through every format and leaves no skeleton at the end.
111
+
112
+ ## Commands
113
+
114
+ ```bash
115
+ pnpm --filter @wallarm-org/gen-ui test:run # rstest, every format
116
+ pnpm --filter @wallarm-org/gen-ui typecheck
117
+ pnpm --filter @wallarm-org/gen-ui build # rslib → dist/, same layout as src/
118
+ ```
119
+
120
+ The playground app has two demos that replay the fixtures: `/gen-ui` (answers in the Wally chat)
121
+ and `/gen-ui-playground` (pick a prompt, watch the agent lay out the answer while the app fills its
122
+ data). The docs site embeds the same `GenUiPlayground` on its Generative UI playground page.
123
+
124
+ ```bash
125
+ pnpm turbo run build --filter=@wallarm-org/design-system # the playground reads the built theme
126
+ pnpm --filter @wallarm-org/playground dev
127
+ ```
128
+
129
+ ## Bundle size
130
+
131
+ What each entry point adds to an app, minified (gzip), with React and the DS external. Measured
132
+ with esbuild from `dist` on 2026-11-12; re-measure after a dependency bump.
133
+
134
+ | Entry | Size | Most of it |
135
+ | --- | --- | --- |
136
+ | `.` (the catalog) | 110K (33K) | zod 4 |
137
+ | `/a2ui` + the catalog | 498K (137K) | `@a2ui/web_core`, zod 3 and zod 4 |
138
+ | `/a2ui/express` | 634K (176K) | `@a2ui/web_core`, `antlr4ng`, `@a2ui/agent` |
139
+ | `/json-render` + the catalog | 532K (119K) | zod 4 with all its locales |
140
+ | `/openui` + the catalog | 564K (132K) | zod 4 with all its locales, `@openuidev/lang-core` |
141
+
142
+ - Import zod as `import * as z from 'zod'`. `import { z } from 'zod'` keeps every zod locale in the
143
+ bundle (443K instead of 83K). json-render and OpenUI import it that way themselves, so their entries still pay for them.
144
+ - Express and the prompt have their own entry points because `@a2ui/agent` and its parser are most
145
+ of the weight. Compile Express on the server when you can, and send the browser A2UI JSON.
146
+ - `@a2ui/agent` imports `fs` and `path` in modules gen-ui doesn't use. Rspack drops them; a
147
+ bundler that resolves them anyway needs `fs: false, path: false` as fallbacks.
148
+
149
+ ## Why two versions of zod
150
+
151
+ json-render needs zod 4, and the definitions are in zod 4. `@a2ui/web_core` pins zod 3 and reads
152
+ zod 3 internals to decide what binds, what's an action and what holds children.
153
+ `a2ui/toA2uiSchema.ts` rewrites each schema with the `zod/v3` API that zod 4 ships. The two
154
+ packages' TS types don't match, so `a2ui/catalog.tsx` casts at that one boundary.
@@ -0,0 +1,16 @@
1
+ import { type FC } from 'react';
2
+ import { type Catalog, type ComponentApi, type RendererErrorMessage } from '@a2ui/web_core';
3
+ import type { GenUiAction, GenUiActionHandler } from '../core/define.js';
4
+ import type { A2uiMessage } from './catalog.js';
5
+ export interface A2uiSurfacesProps {
6
+ catalog: Catalog<ComponentApi>;
7
+ /** Every message the agent has sent so far, in order; new ones are appended while it streams. */
8
+ messages: readonly A2uiMessage[];
9
+ /** The only action names that reach `onAction`; anything else the agent names is dropped. */
10
+ actions: readonly GenUiAction[];
11
+ onAction: GenUiActionHandler;
12
+ /** Messages the processor rejected (bad ids, unknown surface, catalog mismatch). Default: console.warn. */
13
+ onError?: (errors: readonly RendererErrorMessage[]) => void;
14
+ }
15
+ /** Feeds the messages to one MessageProcessor and renders every surface it holds. */
16
+ export declare const A2uiSurfaces: FC<A2uiSurfacesProps>;
@@ -0,0 +1,61 @@
1
+ import { jsx } from "react/jsx-runtime";
2
+ import { useEffect, useRef, useState } from "react";
3
+ import { A2uiSurface } from "@a2ui/react";
4
+ import { MessageProcessor } from "@a2ui/web_core";
5
+ import { SurfaceBoundary } from "../render/SurfaceBoundary.js";
6
+ import { warn } from "../render/warn.js";
7
+ const A2uiSurfaces = ({ catalog, messages, actions, onAction, onError = warnErrors })=>{
8
+ const latest = useRef({
9
+ actions,
10
+ onAction,
11
+ onError
12
+ });
13
+ useEffect(()=>{
14
+ latest.current = {
15
+ actions,
16
+ onAction,
17
+ onError
18
+ };
19
+ });
20
+ const [processor] = useState(()=>new MessageProcessor([
21
+ catalog
22
+ ], (action)=>{
23
+ const { actions: allowed, onAction: dispatch } = latest.current;
24
+ if (allowed.some((a)=>a.name === action.name)) dispatch(action.name, action.context);
25
+ else warn(`dropped A2UI action '${action.name}', not in the allowlist`);
26
+ }));
27
+ const [surfaces, setSurfaces] = useState(()=>[
28
+ ...processor.model.surfacesMap.values()
29
+ ]);
30
+ useEffect(()=>{
31
+ const sync = ()=>setSurfaces([
32
+ ...processor.model.surfacesMap.values()
33
+ ]);
34
+ const created = processor.onSurfaceCreated(sync);
35
+ const deleted = processor.onSurfaceDeleted(sync);
36
+ return ()=>{
37
+ created.unsubscribe();
38
+ deleted.unsubscribe();
39
+ };
40
+ }, [
41
+ processor
42
+ ]);
43
+ const fed = useRef(0);
44
+ useEffect(()=>{
45
+ if (messages.length <= fed.current) return;
46
+ const batch = messages.slice(fed.current);
47
+ fed.current = messages.length;
48
+ const errors = processor.processMessagesWithErrors(batch);
49
+ if (errors.length) latest.current.onError(errors);
50
+ }, [
51
+ messages,
52
+ processor
53
+ ]);
54
+ return surfaces.map((surface)=>/*#__PURE__*/ jsx(SurfaceBoundary, {
55
+ children: /*#__PURE__*/ jsx(A2uiSurface, {
56
+ surface: surface
57
+ })
58
+ }, surface.id));
59
+ };
60
+ const warnErrors = (errors)=>warn('A2UI errors', errors);
61
+ export { A2uiSurfaces };
@@ -0,0 +1,16 @@
1
+ /**
2
+ * The A2UI catalog: what the agent is prompted with (`toJson()`) and how the React renderer draws
3
+ * each entry. The children of a container wait in a ChildSlot until the agent has sent them.
4
+ */
5
+ import { Catalog, type ComponentApi } from '@a2ui/web_core';
6
+ import type { AgentToRendererMessage } from '@a2ui/web_core/v1_0';
7
+ import type { GenUiComponent } from '../core/define.js';
8
+ /** A v1.0 agent → renderer message: createSurface, updateComponents, updateDataModel, deleteSurface. */
9
+ export type A2uiMessage = AgentToRendererMessage;
10
+ export declare const WALLARM_A2UI_CATALOG_ID = "https://wallarm.github.io/design-system/gen-ui/catalog/v1.json";
11
+ /**
12
+ * A2UI glue: each component's zod props become an A2UI API (bindable leaves, one Action prop
13
+ * per event, a ChildList when it has children) and its View renders the binder's resolved props.
14
+ * `toJson()` on the result is the catalog document the agent is prompted with.
15
+ */
16
+ export declare const createA2uiCatalog: (components: readonly GenUiComponent[], id?: string) => Catalog<ComponentApi>;
@@ -0,0 +1,66 @@
1
+ import { jsx } from "react/jsx-runtime";
2
+ import { useSyncExternalStore } from "react";
3
+ import { createComponentImplementation, useA2uiSurface } from "@a2ui/react";
4
+ import { Catalog } from "@a2ui/web_core";
5
+ import { GuardedView } from "../render/GuardedView.js";
6
+ import { Placeholder } from "../render/Placeholder.js";
7
+ import { A2UI_ACTION, A2UI_CHILD_LIST, toA2uiProps } from "./toA2uiSchema.js";
8
+ const WALLARM_A2UI_CATALOG_ID = 'https://wallarm.github.io/design-system/gen-ui/catalog/v1.json';
9
+ const implement = createComponentImplementation;
10
+ const createA2uiCatalog = (components, id = WALLARM_A2UI_CATALOG_ID)=>new Catalog(id, '1.0', components.map((component)=>{
11
+ const { name, description, props, events = [], hasChildren } = component;
12
+ const schema = toA2uiProps(props).extend(Object.fromEntries(events.map((event)=>[
13
+ event,
14
+ A2UI_ACTION.optional()
15
+ ]))).extend(hasChildren ? {
16
+ children: A2UI_CHILD_LIST.optional()
17
+ } : {}).describe(description);
18
+ const impl = implement({
19
+ name,
20
+ schema
21
+ }, ({ props: resolved, buildChild, context })=>{
22
+ const { children, ...rest } = resolved;
23
+ const values = rest;
24
+ const raw = context.componentModel.properties.children;
25
+ const rawIds = Array.isArray(raw) ? raw : void 0;
26
+ const emit = (event)=>{
27
+ const action = values[event];
28
+ if ('function' == typeof action) action();
29
+ };
30
+ return /*#__PURE__*/ jsx(GuardedView, {
31
+ component: component,
32
+ props: values,
33
+ emit: emit,
34
+ children: children?.map((child, index)=>{
35
+ const ref = 'string' == typeof child ? {
36
+ id: child,
37
+ basePath: void 0
38
+ } : child;
39
+ const agentId = rawIds?.[index];
40
+ return /*#__PURE__*/ jsx(ChildSlot, {
41
+ id: ref.id,
42
+ agentId: 'string' == typeof agentId ? agentId : void 0,
43
+ basePath: ref.basePath,
44
+ buildChild: buildChild
45
+ }, `${ref.id}@${ref.basePath ?? ''}`);
46
+ })
47
+ });
48
+ });
49
+ return impl;
50
+ }));
51
+ const ChildSlot = ({ id, agentId, basePath, buildChild })=>{
52
+ const components = useA2uiSurface()?.componentsModel;
53
+ const subscribe = (onChange)=>{
54
+ const created = components?.onCreated.subscribe(onChange);
55
+ const deleted = components?.onDeleted.subscribe(onChange);
56
+ return ()=>{
57
+ created?.unsubscribe();
58
+ deleted?.unsubscribe();
59
+ };
60
+ };
61
+ const arrived = useSyncExternalStore(subscribe, ()=>!agentId || (components?.has(agentId) ?? true));
62
+ return arrived || !agentId ? buildChild(id, basePath) : /*#__PURE__*/ jsx(Placeholder, {
63
+ id: agentId
64
+ });
65
+ };
66
+ export { WALLARM_A2UI_CATALOG_ID, createA2uiCatalog };
@@ -0,0 +1,4 @@
1
+ import { type CheckOptions, type GenUiIssue } from '../core/check.js';
2
+ import type { A2uiMessage } from './catalog.js';
3
+ /** The agent's mistakes in a finished A2UI answer (one surface), per component. */
4
+ export declare const checkA2uiAnswer: (messages: readonly A2uiMessage[], options: CheckOptions) => GenUiIssue[];
@@ -0,0 +1,34 @@
1
+ import { checkNodes } from "../core/check.js";
2
+ import { isRecord } from "../core/guards.js";
3
+ const isBinding = (value)=>isRecord(value) && ('@path' in value || '@call' in value);
4
+ const checkA2uiAnswer = (messages, options)=>{
5
+ const byId = new Map();
6
+ for (const message of messages){
7
+ const components = 'createSurface' in message ? message.createSurface.components : 'updateComponents' in message ? message.updateComponents.components : void 0;
8
+ for (const raw of components ?? [])byId.set(raw.id, toNode(raw, options));
9
+ }
10
+ return checkNodes([
11
+ ...byId.values()
12
+ ], 'root', isBinding, options);
13
+ };
14
+ const toNode = ({ id, component, children, ...rest }, { components })=>{
15
+ const events = components.find((c)=>c.name === component)?.events ?? [];
16
+ const props = Object.fromEntries(Object.entries(rest).filter(([key])=>!events.includes(key)));
17
+ const actions = events.flatMap((event)=>{
18
+ const value = rest[event];
19
+ return isRecord(value) && isRecord(value.event) && 'string' == typeof value.event.name ? [
20
+ value.event.name
21
+ ] : [];
22
+ });
23
+ const ids = Array.isArray(children) ? children.filter((child)=>'string' == typeof child) : isRecord(children) && 'string' == typeof children.componentId ? [
24
+ children.componentId
25
+ ] : [];
26
+ return {
27
+ id,
28
+ type: component,
29
+ props,
30
+ children: ids,
31
+ actions
32
+ };
33
+ };
34
+ export { checkA2uiAnswer };
@@ -0,0 +1,38 @@
1
+ /**
2
+ * A2UI Express: the compact text a model writes instead of A2UI JSON, one statement per
3
+ * component (`m = Metric("Attacks", $/kpi/attacks, "compact")`). Arguments follow the order of
4
+ * the entry's props, bindings are `$/path`. The official @a2ui/agent compiler turns it into the
5
+ * same v1.0 messages, so everything downstream is unchanged.
6
+ *
7
+ * `@wallarm-org/gen-ui/a2ui/express`: its own entry point, since the compiler is most of its weight.
8
+ */
9
+ import { type FC } from 'react';
10
+ import { ExpressParser } from '@a2ui/agent';
11
+ import type { Catalog, ComponentApi } from '@a2ui/web_core';
12
+ import type { GenUiAction, GenUiActionHandler } from '../core/define.js';
13
+ import type { A2uiMessage } from './catalog.js';
14
+ type A2uiCatalog = Catalog<ComponentApi>;
15
+ /** A compiler (and decompiler) for one surface. */
16
+ export declare const createExpressParser: (catalog: A2uiCatalog, surfaceId: string) => ExpressParser;
17
+ /**
18
+ * Recorded v1.0 messages as the Express a model would have written, one statement per line, in
19
+ * order: feed the lines one by one to replay it streaming. The answer is decompiled as a whole,
20
+ * because Express creates a surface in the scope that defines its root.
21
+ */
22
+ export declare const toExpressLines: (catalog: A2uiCatalog, surfaceId: string, messages: readonly A2uiMessage[]) => string[];
23
+ export interface ExpressSurfacesProps {
24
+ catalog: A2uiCatalog;
25
+ surfaceId: string;
26
+ /** The Express text so far, in the chunks it arrived in; new chunks are appended. */
27
+ chunks: readonly string[];
28
+ actions: readonly GenUiAction[];
29
+ onAction: GenUiActionHandler;
30
+ /** A block the compiler rejected, or a message the renderer did. Default: console.warn. */
31
+ onError?: (errors: readonly unknown[]) => void;
32
+ }
33
+ /**
34
+ * Renders a streaming Express answer: each new chunk goes into one streaming compiler, which
35
+ * emits v1.0 messages as soon as they're complete, and those render like any A2UI answer.
36
+ */
37
+ export declare const ExpressSurfaces: FC<ExpressSurfacesProps>;
38
+ export {};
@@ -0,0 +1,64 @@
1
+ import { jsx } from "react/jsx-runtime";
2
+ import { useEffect, useRef, useState } from "react";
3
+ import { ExpressParser } from "@a2ui/agent";
4
+ import { A2uiSurfaces } from "./A2uiSurfaces.js";
5
+ const createExpressParser = (catalog, surfaceId)=>new ExpressParser(catalog, surfaceId, '1.0');
6
+ const toExpressLines = (catalog, surfaceId, messages)=>createExpressParser(catalog, surfaceId).decompile(withRootInCreate(messages)).split('\n').filter((line)=>'' !== line.trim());
7
+ const withRootInCreate = (messages)=>{
8
+ const holdsRoot = (m)=>'updateComponents' in m && m.updateComponents.components.some((c)=>'root' === c.id);
9
+ const create = messages.find((m)=>'createSurface' in m);
10
+ const rootUpdate = messages.find(holdsRoot);
11
+ if (!create || !('createSurface' in create) || create.createSurface.components || !rootUpdate) return [
12
+ ...messages
13
+ ];
14
+ if (!('updateComponents' in rootUpdate)) return [
15
+ ...messages
16
+ ];
17
+ const merged = {
18
+ ...create,
19
+ createSurface: {
20
+ ...create.createSurface,
21
+ components: rootUpdate.updateComponents.components
22
+ }
23
+ };
24
+ return messages.flatMap((m)=>m === create ? [
25
+ merged
26
+ ] : m === rootUpdate ? [] : [
27
+ m
28
+ ]);
29
+ };
30
+ const ExpressSurfaces = ({ catalog, surfaceId, chunks, actions, onAction, onError })=>{
31
+ const [stream] = useState(()=>createExpressParser(catalog, surfaceId).openStream({
32
+ wrapped: false,
33
+ afterRejectedBlock: 'continue'
34
+ }));
35
+ const [messages, setMessages] = useState([]);
36
+ const onErrorRef = useRef(onError);
37
+ useEffect(()=>{
38
+ onErrorRef.current = onError;
39
+ });
40
+ const fed = useRef(0);
41
+ useEffect(()=>{
42
+ if (chunks.length <= fed.current) return;
43
+ const parts = chunks.slice(fed.current).flatMap((chunk)=>stream.feed(`${chunk}\n`));
44
+ fed.current = chunks.length;
45
+ const compiled = parts.flatMap((part)=>'a2ui' === part.type ? part.a2ui : []);
46
+ const rejected = parts.filter((part)=>'error' === part.type);
47
+ if (compiled.length) setMessages((list)=>[
48
+ ...list,
49
+ ...compiled
50
+ ]);
51
+ if (rejected.length) onErrorRef.current?.(rejected);
52
+ }, [
53
+ chunks,
54
+ stream
55
+ ]);
56
+ return /*#__PURE__*/ jsx(A2uiSurfaces, {
57
+ catalog: catalog,
58
+ messages: messages,
59
+ actions: actions,
60
+ onAction: onAction,
61
+ onError: onError
62
+ });
63
+ };
64
+ export { ExpressSurfaces, createExpressParser, toExpressLines };
@@ -0,0 +1,8 @@
1
+ /**
2
+ * `@wallarm-org/gen-ui/a2ui`: render the catalog from A2UI v1.0 messages. What needs the A2UI
3
+ * agent library (and its parser) has its own entry points: `/a2ui/express` and `/a2ui/prompt`.
4
+ */
5
+ export { A2uiSurfaces, type A2uiSurfacesProps } from './A2uiSurfaces.js';
6
+ export { type A2uiMessage, createA2uiCatalog, WALLARM_A2UI_CATALOG_ID } from './catalog.js';
7
+ export { checkA2uiAnswer } from './check.js';
8
+ export { toA2uiProps, toA2uiSchema } from './toA2uiSchema.js';
@@ -0,0 +1,4 @@
1
+ export { A2uiSurfaces } from "./A2uiSurfaces.js";
2
+ export { WALLARM_A2UI_CATALOG_ID, createA2uiCatalog } from "./catalog.js";
3
+ export { checkA2uiAnswer } from "./check.js";
4
+ export { toA2uiProps, toA2uiSchema } from "./toA2uiSchema.js";
@@ -0,0 +1,16 @@
1
+ import type { Catalog, ComponentApi } from '@a2ui/web_core';
2
+ import type { GenUiAction } from '../core/define.js';
3
+ export interface A2uiPromptOptions {
4
+ /** What the model writes: A2UI Express (compact, the default) or the v1.0 JSON messages. */
5
+ format?: 'express' | 'json';
6
+ /** The actions the app accepts: the model may fire only these. */
7
+ actions: readonly GenUiAction[];
8
+ }
9
+ /**
10
+ * `@wallarm-org/gen-ui/a2ui/prompt`, meant for the server that calls the model.
11
+ *
12
+ * The system prompt for an A2UI agent: the format's rules and a signature per catalog entry (from
13
+ * @a2ui/agent), plus the app's actions, which an A2UI catalog doesn't carry. Append your own
14
+ * instructions, e.g. which data paths the app fills.
15
+ */
16
+ export declare const a2uiPrompt: (catalog: Catalog<ComponentApi>, { format, actions }: A2uiPromptOptions) => string;
@@ -0,0 +1,17 @@
1
+ import { DirectJsonFormat, ExpressFormat } from "@a2ui/agent";
2
+ const a2uiPrompt = (catalog, { format = 'express', actions })=>{
3
+ const generator = 'express' === format ? new ExpressFormat([
4
+ catalog
5
+ ], {
6
+ version: '1.0'
7
+ }).promptGenerator : new DirectJsonFormat([
8
+ catalog
9
+ ]).promptGenerator;
10
+ return [
11
+ generator.generate(),
12
+ '## Actions',
13
+ 'A button fires an action by name; the app ignores any name not listed here.',
14
+ ...actions.map((a)=>`- ${a.name}: ${a.description}`)
15
+ ].join('\n');
16
+ };
17
+ export { a2uiPrompt };
@@ -0,0 +1,19 @@
1
+ import type * as z from 'zod';
2
+ import { z as z3 } from 'zod/v3';
3
+ /**
4
+ * Rewrites a gen-ui zod 4 props schema into the zod 3 schema A2UI's binder walks.
5
+ *
6
+ * @a2ui/web_core pins zod 3 and recognises its schemas by `_def.typeName` and `REF:`
7
+ * descriptions (not `instanceof`), so `zod/v3` from our zod 4 works next to its own copy.
8
+ * - string / number / boolean leaves become `DynamicString` / `DynamicNumber` / `DynamicBoolean`:
9
+ * a literal, or `{ "@path": "/..." }` into the surface's data model.
10
+ * - arrays and objects take a literal, or one binding for the whole value.
11
+ * - enums stay literal.
12
+ * Field descriptions carry over, except onto a dynamic leaf or any wrapper of one: the binder
13
+ * finds `REF:DynamicString` and the like by the first description up the wrapper chain.
14
+ */
15
+ export declare const toA2uiSchema: (schema: z.ZodType, dynamic?: boolean, description?: string | undefined) => z3.ZodTypeAny;
16
+ /** The props object itself: its fields are bound one by one, not as a whole. */
17
+ export declare const toA2uiProps: (props: z.ZodObject) => z3.AnyZodObject;
18
+ export declare const A2UI_ACTION: z3.ZodTypeAny;
19
+ export declare const A2UI_CHILD_LIST: z3.ZodTypeAny;
@@ -0,0 +1,61 @@
1
+ import { CommonSchemas } from "@a2ui/web_core/v1_0";
2
+ import { z } from "zod/v3";
3
+ const toA2uiSchema = (schema, dynamic = true, description = schema.description)=>{
4
+ const def = schema._zod.def;
5
+ const leaf = 'string' === def.type || 'number' === def.type || 'boolean' === def.type;
6
+ const out = convert(schema, dynamic, description);
7
+ return description && !(dynamic && leaf) && 'optional' !== def.type ? out.describe(description) : out;
8
+ };
9
+ const convert = (schema, dynamic, description)=>{
10
+ const def = schema._zod.def;
11
+ switch(def.type){
12
+ case 'string':
13
+ return dynamic ? common(CommonSchemas.DynamicString) : z.string();
14
+ case 'number':
15
+ return dynamic ? common(CommonSchemas.DynamicNumber) : z.number();
16
+ case 'boolean':
17
+ return dynamic ? common(CommonSchemas.DynamicBoolean) : z.boolean();
18
+ case 'enum':
19
+ {
20
+ const values = Object.values(def.entries).map(String);
21
+ return z["enum"](values);
22
+ }
23
+ case 'optional':
24
+ return toA2uiSchema(def.innerType, dynamic, description ?? def.innerType.description).optional();
25
+ case 'array':
26
+ {
27
+ const literal = z.array(toA2uiSchema(def.element, false));
28
+ return dynamic ? z.union([
29
+ literal,
30
+ common(CommonSchemas.DataBinding)
31
+ ]) : literal;
32
+ }
33
+ case 'object':
34
+ {
35
+ const { shape, catchall } = def;
36
+ const fixed = z.object(Object.fromEntries(Object.entries(shape).map(([key, value])=>[
37
+ key,
38
+ toA2uiSchema(value, false)
39
+ ])));
40
+ const literal = catchall ? fixed.catchall(toA2uiSchema(catchall, false)) : fixed;
41
+ return dynamic ? z.union([
42
+ literal,
43
+ common(CommonSchemas.DataBinding)
44
+ ]) : literal;
45
+ }
46
+ case 'nullable':
47
+ return toA2uiSchema(def.innerType, dynamic, description).nullable();
48
+ case 'unknown':
49
+ return z.unknown();
50
+ default:
51
+ throw new Error(`gen-ui: prop type '${def.type}' has no A2UI mapping`);
52
+ }
53
+ };
54
+ const toA2uiProps = (props)=>z.object(Object.fromEntries(Object.entries(props.shape).map(([key, value])=>[
55
+ key,
56
+ toA2uiSchema(value)
57
+ ])));
58
+ const common = (schema)=>schema;
59
+ const A2UI_ACTION = common(CommonSchemas.Action);
60
+ const A2UI_CHILD_LIST = common(CommonSchemas.ChildList);
61
+ export { A2UI_ACTION, A2UI_CHILD_LIST, toA2uiProps, toA2uiSchema };
@@ -0,0 +1,11 @@
1
+ import * as z from 'zod';
2
+ export declare const AlertComponent: import("../index.js").GenUiComponent<z.ZodObject<{
3
+ title: z.ZodString;
4
+ description: z.ZodOptional<z.ZodString>;
5
+ tone: z.ZodOptional<z.ZodEnum<{
6
+ destructive: "destructive";
7
+ info: "info";
8
+ success: "success";
9
+ warning: "warning";
10
+ }>>;
11
+ }, z.core.$strip>>;
@@ -0,0 +1,43 @@
1
+ import { jsx, jsxs } from "react/jsx-runtime";
2
+ import { Alert, AlertContent, AlertDescription, AlertIcon, AlertTitle } from "@wallarm-org/design-system/Alert";
3
+ import { Skeleton } from "@wallarm-org/design-system/Skeleton";
4
+ import { defineComponent, subsetOf } from "../core/define.js";
5
+ import * as __rspack_external_zod from "zod";
6
+ const AlertComponent = defineComponent({
7
+ name: 'Alert',
8
+ description: 'A highlighted message about something the user should know or act on.',
9
+ props: __rspack_external_zod.object({
10
+ title: __rspack_external_zod.string(),
11
+ description: __rspack_external_zod.string().optional(),
12
+ tone: __rspack_external_zod["enum"](subsetOf()([
13
+ 'info',
14
+ 'warning',
15
+ 'destructive',
16
+ 'success'
17
+ ])).optional()
18
+ }),
19
+ Skeleton: ()=>/*#__PURE__*/ jsx(Skeleton, {
20
+ width: "100%",
21
+ height: "56px",
22
+ rounded: 12,
23
+ transparent: false
24
+ }),
25
+ View: ({ props })=>/*#__PURE__*/ jsxs(Alert, {
26
+ color: props.tone ?? 'info',
27
+ minWidth: 0,
28
+ children: [
29
+ /*#__PURE__*/ jsx(AlertIcon, {}),
30
+ /*#__PURE__*/ jsxs(AlertContent, {
31
+ children: [
32
+ /*#__PURE__*/ jsx(AlertTitle, {
33
+ children: props.title
34
+ }),
35
+ props.description && /*#__PURE__*/ jsx(AlertDescription, {
36
+ children: props.description
37
+ })
38
+ ]
39
+ })
40
+ ]
41
+ })
42
+ });
43
+ export { AlertComponent };