@brinkcommerce/agentic-shopping-sdk 0.1.0-alpha.0 → 0.1.0-alpha.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 +73 -34
- package/package.json +3 -2
package/README.md
CHANGED
|
@@ -12,9 +12,7 @@ Node 20 or later. The SDK uses the global `fetch` and `crypto`, and older versio
|
|
|
12
12
|
|
|
13
13
|
## Why there are two halves
|
|
14
14
|
|
|
15
|
-
A browser cannot call the agent.
|
|
16
|
-
|
|
17
|
-
Every deployment therefore looks like this:
|
|
15
|
+
A browser cannot call the agent. The client secret that mints the bearer token must never reach a page, and the agent sends no CORS headers. So every deployment has a server in the middle:
|
|
18
16
|
|
|
19
17
|
```text
|
|
20
18
|
browser ──POST /api/chat──▶ your server ──POST /invocations──▶ the agent
|
|
@@ -24,6 +22,74 @@ browser ──POST /api/chat──▶ your server ──POST /invocations─
|
|
|
24
22
|
|
|
25
23
|
`@brinkcommerce/agentic-shopping-sdk/server` is the middle box. `@brinkcommerce/agentic-shopping-sdk` is what the browser imports. It holds no credentials and never calls the agent.
|
|
26
24
|
|
|
25
|
+
## Quickstart
|
|
26
|
+
|
|
27
|
+
Brink provides the four `AGENT_*` values. `storeGroupId` and `market` are the store your storefront is already rendering.
|
|
28
|
+
|
|
29
|
+
```ts
|
|
30
|
+
// lib/agent.ts — server only. Module scope, so one token serves every turn.
|
|
31
|
+
import { AgentShoppingClient } from '@brinkcommerce/agentic-shopping-sdk/server'
|
|
32
|
+
|
|
33
|
+
export const client = new AgentShoppingClient({
|
|
34
|
+
runtimeArn: process.env.AGENT_RUNTIME_ARN!,
|
|
35
|
+
tokenUrl: process.env.AGENT_TOKEN_URL!,
|
|
36
|
+
clientId: process.env.AGENT_CLIENT_ID!,
|
|
37
|
+
clientSecret: process.env.AGENT_CLIENT_SECRET!,
|
|
38
|
+
})
|
|
39
|
+
```
|
|
40
|
+
|
|
41
|
+
```ts
|
|
42
|
+
// app/api/chat/route.ts
|
|
43
|
+
import { createChatRoute } from '@brinkcommerce/agentic-shopping-sdk/server'
|
|
44
|
+
import { client } from '@/lib/agent'
|
|
45
|
+
|
|
46
|
+
export const runtime = 'nodejs'
|
|
47
|
+
export const maxDuration = 60
|
|
48
|
+
export const POST = createChatRoute(client)
|
|
49
|
+
```
|
|
50
|
+
|
|
51
|
+
```ts
|
|
52
|
+
// the browser
|
|
53
|
+
import { parseWireEvents, createSessionId, isWireEvent } from '@brinkcommerce/agentic-shopping-sdk'
|
|
54
|
+
|
|
55
|
+
const sessionId = createSessionId() // one per conversation, not one per mount
|
|
56
|
+
|
|
57
|
+
const response = await fetch('/api/chat', {
|
|
58
|
+
method: 'POST',
|
|
59
|
+
headers: { 'Content-Type': 'application/json' },
|
|
60
|
+
body: JSON.stringify({ prompt, sessionId, storeGroupId, market }),
|
|
61
|
+
})
|
|
62
|
+
if (!response.ok || !response.body) throw new Error(`the turn failed: ${response.status}`)
|
|
63
|
+
|
|
64
|
+
for await (const event of parseWireEvents(response.body)) {
|
|
65
|
+
if (isWireEvent(event, 'text')) appendText(event.content)
|
|
66
|
+
if (isWireEvent(event, 'products')) setProducts(event.products) // replaces, never appends
|
|
67
|
+
if (isWireEvent(event, 'done')) setDone()
|
|
68
|
+
}
|
|
69
|
+
```
|
|
70
|
+
|
|
71
|
+
Then render the reply, with each card under the paragraph that names it:
|
|
72
|
+
|
|
73
|
+
```tsx
|
|
74
|
+
import { interleaveProducts, formatPrice } from '@brinkcommerce/agentic-shopping-sdk'
|
|
75
|
+
|
|
76
|
+
const { segments } = interleaveProducts(text, products)
|
|
77
|
+
|
|
78
|
+
segments.map((segment) =>
|
|
79
|
+
segment.type === 'text' ? (
|
|
80
|
+
<Markdown>{segment.content}</Markdown>
|
|
81
|
+
) : (
|
|
82
|
+
<Card
|
|
83
|
+
key={segment.product.productId}
|
|
84
|
+
product={segment.product}
|
|
85
|
+
price={formatPrice(segment.product.salePriceAmount, segment.product.currencyCode, 'sv-SE')}
|
|
86
|
+
/>
|
|
87
|
+
)
|
|
88
|
+
)
|
|
89
|
+
```
|
|
90
|
+
|
|
91
|
+
That is a working integration. Everything below is the detail behind it, starting with the four traps that break one.
|
|
92
|
+
|
|
27
93
|
---
|
|
28
94
|
|
|
29
95
|
## Four things that break an integration
|
|
@@ -41,17 +107,6 @@ Each one fails quietly. None of them throws.
|
|
|
41
107
|
|
|
42
108
|
## Setup
|
|
43
109
|
|
|
44
|
-
```ts
|
|
45
|
-
import { AgentShoppingClient } from '@brinkcommerce/agentic-shopping-sdk/server'
|
|
46
|
-
|
|
47
|
-
export const client = new AgentShoppingClient({
|
|
48
|
-
runtimeArn: process.env.AGENT_RUNTIME_ARN!,
|
|
49
|
-
tokenUrl: process.env.AGENT_TOKEN_URL!,
|
|
50
|
-
clientId: process.env.AGENT_CLIENT_ID!,
|
|
51
|
-
clientSecret: process.env.AGENT_CLIENT_SECRET!,
|
|
52
|
-
})
|
|
53
|
-
```
|
|
54
|
-
|
|
55
110
|
Brink provides all four values. `scope` is optional and defaults to `agentic-shopping/invoke`.
|
|
56
111
|
|
|
57
112
|
A runtime ARN looks like this:
|
|
@@ -72,14 +127,6 @@ The client mints a token on the first turn. It caches it in the process until a
|
|
|
72
127
|
|
|
73
128
|
## Next.js route handler
|
|
74
129
|
|
|
75
|
-
```ts
|
|
76
|
-
// app/api/chat/route.ts
|
|
77
|
-
import { createChatRoute } from '@brinkcommerce/agentic-shopping-sdk/server'
|
|
78
|
-
import { client } from '@/lib/agent'
|
|
79
|
-
|
|
80
|
-
export const POST = createChatRoute(client)
|
|
81
|
-
```
|
|
82
|
-
|
|
83
130
|
`createChatRoute` takes a standard `Request` and returns a `Response`. It has no Next.js dependency, so it works in any framework with the same shape.
|
|
84
131
|
|
|
85
132
|
Four things to get right in a Next.js handler:
|
|
@@ -244,17 +291,11 @@ One thing follows from that while a reply streams: a card stays in `unmatched` u
|
|
|
244
291
|
|
|
245
292
|
## The reply is markdown
|
|
246
293
|
|
|
247
|
-
The agent writes markdown into `text`: `**bold**` around product names, and `##` headings on longer answers.
|
|
294
|
+
The agent writes markdown into `text`: `**bold**` around product names, and `##` headings on longer answers. No tables, no bullet lists and no strikethrough, so a renderer plugin for those has nothing to render.
|
|
248
295
|
|
|
249
296
|
`interleaveProducts` returns data, not markup, so rendering is yours to do. Use a real markdown renderer rather than a regex.
|
|
250
297
|
|
|
251
|
-
**A segment never carries half a markdown construct**, so you can send each one to a renderer on its own. A segment boundary is always a blank line, and the agent opens and closes `**bold**` on one line
|
|
252
|
-
|
|
253
|
-
```text
|
|
254
|
-
**W SPRAY GORE TEX JACKET** [product:2212103-120] — 2,800 SEK
|
|
255
|
-
```
|
|
256
|
-
|
|
257
|
-
The exception is a markdown block that spans a blank line itself: a loose list, or a fenced block with an empty line in it. The agent writes neither today. If it starts, that block renders as two blocks.
|
|
298
|
+
**A segment never carries half a markdown construct**, so you can send each one to a renderer on its own. A segment boundary is always a blank line, and the agent opens and closes `**bold**` on one line.
|
|
258
299
|
|
|
259
300
|
Handle one more thing while a reply streams: a `**` whose closing pair has not arrived yet. Render the unclosed span as plain words, so only the weight arrives late. Hiding it instead makes words appear and disappear as the shopper reads. A marker is the opposite case. Keep it hidden until it closes, because a half-written marker is not text the shopper should ever see.
|
|
260
301
|
|
|
@@ -293,11 +334,9 @@ The cart is Brink's, not the agent's, so it is out of scope here. Use [`@brinkco
|
|
|
293
334
|
|
|
294
335
|
Get one thing right at the handoff: **`productVariantId` is the only id `POST /sessions/items` accepts.** `productParentId` is for the PDP and `productId` is for variant lookups. Neither is ever a cart id. Falling back to one when no variant is selected is a guaranteed rejected add. Gate the add on a chosen variant instead.
|
|
295
336
|
|
|
296
|
-
Wire the cart up properly and a shopper can go from question to purchase without leaving the conversation.
|
|
297
|
-
|
|
298
337
|
## Store context
|
|
299
338
|
|
|
300
|
-
`storeGroupId` and `market` are the store group and market your storefront is already rendering. Send both on every turn. There is no default and no fallback
|
|
339
|
+
`storeGroupId` and `market` are the store group and market your storefront is already rendering. Send both on every turn. There is no default and no fallback.
|
|
301
340
|
|
|
302
341
|
- **`storeGroupId`** is an opaque Brink id. The merchant picks it, and the agent matches it exactly, case included. Pass it whole and do not normalise it. It looks like `sail-racing-se`.
|
|
303
342
|
- **`market`** is the store market's two-letter country code, which the agent upper-cases. `SE`, not `sv-SE` and not `Sweden`.
|
|
@@ -353,7 +392,7 @@ A cached token refused with a 401 is re-minted and the turn is retried once. A t
|
|
|
353
392
|
|
|
354
393
|
So a browser has two failures to handle, and they look nothing alike. A 400 or a 502 is a JSON body with an `error` string, and there is no stream to read. A failed turn is an ordinary 200 stream carrying one apologetic sentence.
|
|
355
394
|
|
|
356
|
-
**A failed turn is not an HTTP failure.**
|
|
395
|
+
**A failed turn is not an HTTP failure.** Four cases arrive as a `text` + `done` pair at **HTTP 200**: a turn that broke mid-answer, a request naming no store, a request naming a store the agent does not serve, and a store conflict. The protocol has no error channel. `response.ok` sees nothing but success, and the shopper gets one apologetic sentence where a reply should be.
|
|
357
396
|
|
|
358
397
|
The store cases are your bug rather than the shopper's, and this SDK makes them unrepresentable. `storeGroupId` and `market` are both required in `AskInput`, and `createChatRoute` rejects a payload missing either with a 400 rather than forwarding a request that would come back as one sentence.
|
|
359
398
|
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@brinkcommerce/agentic-shopping-sdk",
|
|
3
|
-
"version": "0.1.0-alpha.
|
|
3
|
+
"version": "0.1.0-alpha.1",
|
|
4
4
|
"description": "TypeScript SDK for the Brink Commerce agentic shopping agent",
|
|
5
5
|
"license": "MIT",
|
|
6
6
|
"type": "module",
|
|
@@ -30,7 +30,8 @@
|
|
|
30
30
|
"types": "./dist/server.d.ts",
|
|
31
31
|
"import": "./dist/server.js",
|
|
32
32
|
"require": "./dist/server.cjs"
|
|
33
|
-
}
|
|
33
|
+
},
|
|
34
|
+
"./package.json": "./package.json"
|
|
34
35
|
},
|
|
35
36
|
"scripts": {
|
|
36
37
|
"build": "tsup src/index.ts src/server.ts --format esm,cjs --dts --clean",
|