@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.
Files changed (2) hide show
  1. package/README.md +73 -34
  2. 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. There are two reasons, and each one is enough on its own. The client secret that mints the bearer token must never reach a page. The agent endpoint also sends no CORS headers, so the browser would block the request even if the secret were public.
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. That is all it writes today, and what it writes follows its own prompt rather than this SDK's version. It writes no tables, no bullet lists and no strikethrough, so a renderer plugin for those is provisioning for output that never comes.
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, because a storefront that spans several store groups knows which one it is on before the chat opens.
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.** This is the one that surprises people. 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.
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.0",
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",