@rudra-js/a2ui 0.9.0 → 0.9.1
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 +115 -105
- package/package.json +5 -5
package/README.md
CHANGED
|
@@ -6,14 +6,15 @@ rudra-js is an open-source TypeScript library (MIT) you install from npm. A lang
|
|
|
6
6
|
|
|
7
7
|
It is not a hosted platform, a visual or low-code app builder, or a workspace you sign up for.
|
|
8
8
|
|
|
9
|
-
|
|
10
|
-
adds
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
9
|
+
You can use this package to put the block inside the interfaces an [A2UI](https://a2ui.org) agent
|
|
10
|
+
builds. It adds a single component, `RudraRecommendations`, to the catalog your A2UI renderer draws
|
|
11
|
+
from. This component will be placed by the agent, which will also pass hints about what the shopper
|
|
12
|
+
is looking at. It's still up to your server to pick the products and build the block with
|
|
13
|
+
`@rudra-js/core`. To render the block in the browser, this package uses `@rudra-js/react`. For
|
|
14
|
+
Flutter, Angular or any other renderer, your server can send the block as A2UI messages instead (see
|
|
15
|
+
[Flutter, Angular and other renderers](#flutter-angular-and-other-renderers)).
|
|
15
16
|
|
|
16
|
-
It works with version 0.9 of the A2UI protocol
|
|
17
|
+
It works with version 0.9 of the A2UI protocol and uses `@a2ui/react` and `@a2ui/web_core` 0.14.
|
|
17
18
|
|
|
18
19
|
## Install
|
|
19
20
|
|
|
@@ -21,33 +22,37 @@ It works with version 0.9 of the A2UI protocol, using `@a2ui/react` and `@a2ui/w
|
|
|
21
22
|
npm install @rudra-js/a2ui@latest @rudra-js/core@latest @rudra-js/react@latest @rudra-js/attested@latest @a2ui/react@0.14 @a2ui/web_core@0.14 react react-dom
|
|
22
23
|
```
|
|
23
24
|
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
25
|
+
To install and also to upgrade, you will need to use this same command. It doesn't matter if your
|
|
26
|
+
app already uses rudra-js or A2UI.
|
|
27
|
+
Any app that has `@rudra-js/anthropic` will also need to add `@rudra-js/anthropic@latest` to this
|
|
28
|
+
command.
|
|
29
|
+
|
|
30
|
+
All the `@rudra-js` packages are released together. For each package, the peer range for the others
|
|
31
|
+
starts at the version of that package. For a package name with no version, npm will stay inside the
|
|
32
|
+
range your `package.json` already has. If `@latest` is left out, an app on an older core can get an
|
|
33
|
+
`ERESOLVE` error. With no version on `react` and `react-dom`, npm and pnpm will keep a React 18 app
|
|
34
|
+
on 18.
|
|
35
|
+
|
|
36
|
+
Because everything except zod is a peer dependency, your app holds a single copy of each. On npm and
|
|
37
|
+
pnpm, `@rudra-js/core`, `@rudra-js/attested` and `@a2ui/web_core` will be installed for you, but not
|
|
38
|
+
`@a2ui/react`, `@rudra-js/react` or `react`. React can be left out on a server that only builds
|
|
39
|
+
[messages for other renderers](#flutter-angular-and-other-renderers), since those three are
|
|
40
|
+
optional. But those three have to stay in the command for the browser component to load. List the
|
|
41
|
+
rest anyway, because your code imports most of these itself.
|
|
42
|
+
With pnpm your code can only import the packages your `package.json` names. You will need the whole
|
|
43
|
+
list on Yarn because it doesn't install peers. It will also move a name with no version to its
|
|
44
|
+
latest release. So if your app already has React, leave out `react` and `react-dom` on Yarn. On
|
|
45
|
+
pnpm, read [zod](#zod) first.
|
|
46
|
+
|
|
47
|
+
A2UI's type declarations will fail to typecheck if you don't keep `skipLibCheck: true` in your
|
|
48
|
+
`tsconfig.json`. They need `"dom"` in `lib`. Two of their imports will also be refused by `nodenext`
|
|
49
|
+
because these have no file extension.
|
|
45
50
|
|
|
46
51
|
## Three pieces
|
|
47
52
|
|
|
48
53
|
### The catalog your agent is prompted with
|
|
49
54
|
|
|
50
|
-
Put `agentCatalog.toJson()` in your agent's prompt
|
|
55
|
+
Put `agentCatalog.toJson()` in your agent's prompt and have the agent create surfaces with
|
|
51
56
|
`catalogId: agentCatalog.id`. If you have catalog instructions of your own, join them to
|
|
52
57
|
`RUDRA_RECOMMENDATIONS_INSTRUCTIONS`.
|
|
53
58
|
|
|
@@ -65,15 +70,16 @@ export const agentCatalog = new Catalog(
|
|
|
65
70
|
);
|
|
66
71
|
```
|
|
67
72
|
|
|
68
|
-
|
|
73
|
+
You will see a "Recursive reference detected" warning per hint from the first `toJson()` call. It's
|
|
74
|
+
harmless.
|
|
69
75
|
|
|
70
76
|
### Your endpoint
|
|
71
77
|
|
|
72
|
-
`shopperFor` and `candidatesFor` stand in for the session and catalog code you already have.
|
|
73
|
-
`provider: null` calls no model
|
|
78
|
+
`shopperFor` and `candidatesFor` stand in for the session and catalog code you already have. You
|
|
79
|
+
will need to pass your own provider, because `provider: null` calls no model.
|
|
74
80
|
|
|
75
|
-
`generation`
|
|
76
|
-
that mode the model never sees `searchQuery`.
|
|
81
|
+
By default `generation` is `'cohort'`. This shares a single block design between shoppers who look
|
|
82
|
+
alike. In that mode the model never sees `searchQuery`. For the agent's search to matter, you need
|
|
77
83
|
`generation: 'per-shopper'`.
|
|
78
84
|
|
|
79
85
|
```ts
|
|
@@ -113,13 +119,13 @@ export async function POST(request: Request): Promise<Response> {
|
|
|
113
119
|
```
|
|
114
120
|
|
|
115
121
|
- `parseRecommendationRequest` accepts only `currentSku`, `currentCategory`, `searchQuery` and
|
|
116
|
-
`maxItems`. Anything else
|
|
117
|
-
- `fitRecommendationRequest`
|
|
118
|
-
- Pick candidates on your side
|
|
119
|
-
-
|
|
120
|
-
|
|
121
|
-
[SECURITY.md](https://github.com/clivedsouza1010/rudra-js/blob/main/SECURITY.md)).
|
|
122
|
-
- Send back `input.candidates` and `input.bundles
|
|
122
|
+
`maxItems`. Anything else will get a 400.
|
|
123
|
+
- `fitRecommendationRequest` will drop a SKU or category if it's not in any of your candidates.
|
|
124
|
+
- Pick candidates on your side instead of from the hints.
|
|
125
|
+
- There will be a new model call for each new category, `maxItems` or candidate set. With
|
|
126
|
+
`per-shopper` this also goes for each shopper and each new SKU or search. Rate-limit the endpoint
|
|
127
|
+
(see [SECURITY.md](https://github.com/clivedsouza1010/rudra-js/blob/main/SECURITY.md)).
|
|
128
|
+
- Send back `input.candidates` and `input.bundles` instead of your raw catalog.
|
|
123
129
|
|
|
124
130
|
### The browser
|
|
125
131
|
|
|
@@ -153,47 +159,48 @@ const shopCatalog = new Catalog<ReactCatalogComponent>(
|
|
|
153
159
|
export const processor = new MessageProcessor<ReactCatalogComponent>([shopCatalog]);
|
|
154
160
|
```
|
|
155
161
|
|
|
156
|
-
Give the browser catalog the
|
|
157
|
-
agent sends.
|
|
162
|
+
Give the browser catalog the id of the agent catalog, because A2UI will look it up by the
|
|
163
|
+
`catalogId` the agent sends. Use `A2uiSurface` to render the surfaces of the processor.
|
|
158
164
|
|
|
159
165
|
`createRudraRecommendationsComponent` takes `loadRecommendations`, `placeholder` and `onError`. It
|
|
160
|
-
also takes
|
|
166
|
+
also takes all of the `RudraComponent` props except `spec`, `products` and `bundles`.
|
|
161
167
|
|
|
162
168
|
## What the component does
|
|
163
169
|
|
|
164
|
-
- It
|
|
165
|
-
`{ "path": "/viewing/sku" }`. It
|
|
166
|
-
- It leaves out
|
|
167
|
-
|
|
168
|
-
-
|
|
170
|
+
- It will read each hint either as a literal value or from the agent's data model, like
|
|
171
|
+
`{ "path": "/viewing/sku" }`. It will only send the four hints the endpoint accepts.
|
|
172
|
+
- It leaves out any hint that is blank. It also leaves out a hint the server would turn away, like a
|
|
173
|
+
`maxItems` of 40.
|
|
174
|
+
- With a literal of the wrong type, like a numeric SKU, A2UI will refuse the agent's whole message.
|
|
175
|
+
- It waits for the hints to stop changing for 300 ms and then calls `loadRecommendations`.
|
|
169
176
|
- It keeps showing the last block until the next one arrives.
|
|
170
177
|
- `signal` aborts when the hints change or the component unmounts.
|
|
171
|
-
- It
|
|
178
|
+
- It will check the result against core's schemas before it renders any of it.
|
|
172
179
|
- It shows `placeholder` until the first load finishes.
|
|
173
|
-
- If a load fails or the result fails the schema check, it
|
|
174
|
-
|
|
180
|
+
- If a load fails or the result fails the schema check, it will show nothing and call `onError`.
|
|
181
|
+
This is also what happens if the block throws while rendering.
|
|
175
182
|
- Each component on a surface sends its own request.
|
|
176
183
|
|
|
177
184
|
## Where it runs
|
|
178
185
|
|
|
179
|
-
`@rudra-js/a2ui` holds `createRudraRecommendationsComponent` and its types. It
|
|
180
|
-
browser, because `@a2ui/react` needs a DOM.
|
|
181
|
-
`@rudra-js/a2ui/server
|
|
186
|
+
`@rudra-js/a2ui` holds `createRudraRecommendationsComponent` and its types. It can only be loaded in
|
|
187
|
+
the browser, because `@a2ui/react` needs a DOM. For the agent catalog and your endpoint, import from
|
|
188
|
+
`@rudra-js/a2ui/server`. This entry point can run anywhere.
|
|
182
189
|
|
|
183
|
-
In Next.js, a `'use client'` file
|
|
184
|
-
that imports `@rudra-js/a2ui
|
|
185
|
-
client component.
|
|
190
|
+
In Next.js, a `'use client'` file will also run on the server for the first render. To load the file
|
|
191
|
+
that imports `@rudra-js/a2ui`, you will need `next/dynamic` with `ssr: false`. This call has to be
|
|
192
|
+
made from a client component.
|
|
186
193
|
|
|
187
|
-
|
|
188
|
-
render `RudraComponent` on the server
|
|
194
|
+
Any block an agent places won't be seen by a crawler, because A2UI renders in the browser. For a
|
|
195
|
+
product page, render `RudraComponent` on the server as the
|
|
189
196
|
[`@rudra-js/react` README](https://github.com/clivedsouza1010/rudra-js/tree/main/packages/react)
|
|
190
197
|
describes.
|
|
191
198
|
|
|
192
199
|
## Flutter, Angular and other renderers
|
|
193
200
|
|
|
194
201
|
`RudraRecommendations` needs a React renderer. For any other A2UI renderer, build the block on your
|
|
195
|
-
server as A2UI messages that use
|
|
196
|
-
no React
|
|
202
|
+
server as A2UI messages that use just the basic catalog. All renderers ship with this catalog. If
|
|
203
|
+
your server has no React, it only needs these:
|
|
197
204
|
|
|
198
205
|
```sh
|
|
199
206
|
npm install @rudra-js/a2ui@latest @rudra-js/core@latest @rudra-js/attested@latest @a2ui/web_core@0.14
|
|
@@ -215,63 +222,66 @@ export function messagesFor(result: RecommendationResult, locale: string) {
|
|
|
215
222
|
|
|
216
223
|
In [your endpoint](#your-endpoint), return `Response.json(messagesFor(result, shopper.locale))`
|
|
217
224
|
instead of `result`. Skip the [agent catalog](#the-catalog-your-agent-is-prompted-with) and
|
|
218
|
-
[browser](#the-browser) pieces
|
|
219
|
-
|
|
220
|
-
`'rudra_recommendations'
|
|
225
|
+
[browser](#the-browser) pieces and keep `rudraRecommendationsApi` out of your agent's catalog,
|
|
226
|
+
because they are all for `RudraRecommendations`, which needs React. Use your own `surfaceId` in
|
|
227
|
+
place of `'rudra_recommendations'`. It must never be named in your agent's prompt.
|
|
221
228
|
|
|
222
229
|
Your app can call the endpoint with the four hints and give the messages it gets back to its A2UI
|
|
223
|
-
renderer.
|
|
224
|
-
the call has to carry the shopper's session
|
|
225
|
-
|
|
226
|
-
|
|
227
|
-
|
|
228
|
-
|
|
229
|
-
-
|
|
230
|
-
`deleteSurface` for
|
|
231
|
-
|
|
232
|
-
|
|
233
|
-
|
|
234
|
-
|
|
235
|
-
|
|
236
|
-
|
|
237
|
-
`
|
|
238
|
-
|
|
239
|
-
|
|
240
|
-
|
|
241
|
-
|
|
242
|
-
|
|
243
|
-
|
|
244
|
-
|
|
230
|
+
renderer. This is the safer way. Your agent's code can also call it and pass the messages on to the
|
|
231
|
+
client unchanged, but then the call has to carry the shopper's session because `shopperFor` reads
|
|
232
|
+
the shopper from the request. Either way, keep the model away from the messages and from the surface
|
|
233
|
+
of the block, as [SECURITY.md](https://github.com/clivedsouza1010/rudra-js/blob/main/SECURITY.md)
|
|
234
|
+
explains.
|
|
235
|
+
|
|
236
|
+
- These messages will create the surface named `surfaceId` and then fill it. To show a new block
|
|
237
|
+
there, first send `deleteSurface` for this surface. You will get no messages if nothing in the
|
|
238
|
+
block can be shown.
|
|
239
|
+
- Grids and carousels show two cards to a row, because the basic catalog can't fit cards to the
|
|
240
|
+
screen and its `List` doesn't scroll on all renderers.
|
|
241
|
+
- Each product card is a button to open the product's page. By default `hrefForSku` builds the link
|
|
242
|
+
as `/product/<sku>`. `shopUrl` will resolve this link and any relative image URL, because a native
|
|
243
|
+
app has no page to resolve them against. If the image URL of a product is empty or only spaces, no
|
|
244
|
+
image will be shown. An empty link will throw. A link or an image that doesn't end up `http` or
|
|
245
|
+
`https` will also throw.
|
|
246
|
+
- Featured cards look like any other card and banners look the same in all tones, because the basic
|
|
247
|
+
catalog has no styles for them. As nothing carries the `data-rudra-*` attributes the React block
|
|
248
|
+
uses for click tracking, you will need to add a query parameter in `hrefForSku` to count clicks.
|
|
249
|
+
- Prices are formatted on your server for `locale`. If you leave it out, they will use the default
|
|
250
|
+
locale of the server and not the shopper's. You can also pass `formatPrice` and
|
|
251
|
+
`formatBundlePrice` to format prices yourself.
|
|
252
|
+
- All text uses a heading or caption style and never body, because the web renderers only read body
|
|
253
|
+
text as Markdown. Flutter's `genui` and the A2UI SwiftUI renderer read all styles as Markdown, as
|
|
245
254
|
the
|
|
246
255
|
[A2UI guide](https://github.com/a2ui-project/a2ui/blob/main/specification/v0_9_1/docs/basic_catalog_implementation_guide.md)
|
|
247
|
-
asks. So square brackets become parentheses, `<` and `>` become `‹` and
|
|
248
|
-
become spaces. No text can load an image
|
|
249
|
-
as a link. On SwiftUI a tap
|
|
250
|
-
or
|
|
251
|
-
-
|
|
256
|
+
asks. So square brackets become parentheses, while `<` and `>` become `‹` and `›`. Line breaks
|
|
257
|
+
also become spaces. No text can load an image. Only a bare web address or email address can show
|
|
258
|
+
as a link. On SwiftUI a tap will open the link, but on `genui` it does nothing and text that
|
|
259
|
+
starts with `#` or `-` followed by a space shows as Markdown.
|
|
260
|
+
- Neither the rationale nor how the block was generated is ever sent.
|
|
252
261
|
- Flutter's `genui` knows the basic catalog by its id from 0.10.3. For 0.8 to 0.10.2, pass
|
|
253
|
-
`catalogId: 'https://a2ui.org/specification/v0_9/basic_catalog.json'`. On Android 11 and later,
|
|
254
|
-
the app
|
|
255
|
-
|
|
256
|
-
- On `@a2ui/angular` 0.13, register the `BasicCatalog` from `@a2ui/angular/v0_9
|
|
257
|
-
from `@a2ui/angular` has the v1.0 id.
|
|
258
|
-
next to `catalogs`.
|
|
259
|
-
|
|
262
|
+
`catalogId: 'https://a2ui.org/specification/v0_9/basic_catalog.json'`. On Android 11 and later, a
|
|
263
|
+
tap on a card will do nothing if the app doesn't declare `https` links under `<queries>` in its
|
|
264
|
+
manifest.
|
|
265
|
+
- On `@a2ui/angular` 0.13, register the `BasicCatalog` from `@a2ui/angular/v0_9` because the catalog
|
|
266
|
+
from `@a2ui/angular` has the v1.0 id. You also have to pass `useUniversalComponents: true` to
|
|
267
|
+
`provideA2Ui` next to `catalogs`. If you don't, a click on a card will do nothing and all cards
|
|
268
|
+
will try to open their links in a new tab as soon as they render.
|
|
260
269
|
|
|
261
270
|
## zod
|
|
262
271
|
|
|
263
|
-
|
|
264
|
-
|
|
272
|
+
This package depends on zod 3 because A2UI's schemas use zod 3, but the rest of rudra-js uses zod 4.
|
|
273
|
+
npm will install both, each where its own package can find it. With npm you don't need to add either
|
|
274
|
+
version yourself.
|
|
265
275
|
|
|
266
|
-
On pnpm, `@a2ui/react` gets core's zod 4 as its zod peer.
|
|
267
|
-
typecheck with TS2344. If your app has no zod of its own, add zod 3:
|
|
276
|
+
On pnpm, `@a2ui/react` gets core's zod 4 as its zod peer. This makes the browser example fail to
|
|
277
|
+
typecheck with TS2344. If your app has no zod of its own, you will need to add zod 3:
|
|
268
278
|
|
|
269
279
|
```sh
|
|
270
280
|
pnpm add zod@^3.25.76
|
|
271
281
|
```
|
|
272
282
|
|
|
273
|
-
If your app uses zod 4, remove the zod peer from `@a2ui/react` in `.pnpmfile.cjs`.
|
|
274
|
-
it the zod 3 it depends on:
|
|
283
|
+
If your app uses zod 4, you will need to remove the zod peer from `@a2ui/react` in `.pnpmfile.cjs`.
|
|
284
|
+
Then pnpm will give it the zod 3 it depends on:
|
|
275
285
|
|
|
276
286
|
```js
|
|
277
287
|
module.exports = {
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@rudra-js/a2ui",
|
|
3
|
-
"version": "0.9.
|
|
3
|
+
"version": "0.9.1",
|
|
4
4
|
"description": "A2UI component for rudra-js: lets an A2UI agent place a product-recommendation block whose products, prices and wording come from your shop, not from the agent",
|
|
5
5
|
"keywords": [
|
|
6
6
|
"rudra-js",
|
|
@@ -62,8 +62,8 @@
|
|
|
62
62
|
"peerDependencies": {
|
|
63
63
|
"@a2ui/react": "^0.14.0",
|
|
64
64
|
"@a2ui/web_core": "^0.14.0",
|
|
65
|
-
"@rudra-js/core": "^0.9.
|
|
66
|
-
"@rudra-js/react": "^0.9.
|
|
65
|
+
"@rudra-js/core": "^0.9.1",
|
|
66
|
+
"@rudra-js/react": "^0.9.1",
|
|
67
67
|
"react": "^18.2.0 || ^19.0.0"
|
|
68
68
|
},
|
|
69
69
|
"peerDependenciesMeta": {
|
|
@@ -80,8 +80,8 @@
|
|
|
80
80
|
"devDependencies": {
|
|
81
81
|
"@a2ui/react": "0.14.0",
|
|
82
82
|
"@a2ui/web_core": "0.14.0",
|
|
83
|
-
"@rudra-js/core": "0.9.
|
|
84
|
-
"@rudra-js/react": "0.9.
|
|
83
|
+
"@rudra-js/core": "0.9.1",
|
|
84
|
+
"@rudra-js/react": "0.9.1",
|
|
85
85
|
"@types/react": "^19.3.0",
|
|
86
86
|
"@types/react-dom": "^19.3.0",
|
|
87
87
|
"jsdom": "^27.4.0",
|