@agent-cards/commerce-sdk 0.9.0 → 0.10.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.
- package/README.md +103 -8
- package/dist/ack.d.ts +32 -2
- package/dist/ack.js +100 -6
- package/dist/adapters.d.ts +3 -3
- package/dist/adapters.js +1 -1
- package/dist/buy-tool.d.ts +156 -9
- package/dist/buy-tool.js +483 -104
- package/dist/client.d.ts +39 -3
- package/dist/client.js +66 -2
- package/dist/definition.js +9 -5
- package/dist/handler.d.ts +6 -0
- package/dist/handler.js +4 -0
- package/dist/index.d.ts +3 -3
- package/dist/index.js +1 -1
- package/dist/listen.d.ts +2 -0
- package/dist/listen.js +4 -1
- package/dist/messages.d.ts +33 -2
- package/dist/messages.js +82 -5
- package/dist/model-view.d.ts +22 -1
- package/dist/model-view.js +25 -3
- package/package.json +1 -1
package/dist/client.d.ts
CHANGED
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
import { type AgentcardListener, type ListenOptions } from './listen.js';
|
|
2
|
+
import type { ShopperMessage } from './shopper-message.js';
|
|
2
3
|
/** The fetch the client calls: Node 18 and later have one built in. */
|
|
3
4
|
export type FetchLike = (url: string, init: {
|
|
4
5
|
method: string;
|
|
@@ -125,14 +126,28 @@ export interface BuyCartLine {
|
|
|
125
126
|
* A cart as Agentcard answers it. `totalCents` is the all-in figure the shopper sees; `hash` is what a confirm names.
|
|
126
127
|
* Every amount is in the minor unit of `currency`, the store's ISO 4217 code.
|
|
127
128
|
*/
|
|
129
|
+
/** One of the store's own fee or tax lines, as the store labels it: `Delivery Fee`, `Service Fee`, `Estimated Tax`. */
|
|
130
|
+
export interface BuyCartFeeLine {
|
|
131
|
+
label: string;
|
|
132
|
+
amountCents: number;
|
|
133
|
+
}
|
|
128
134
|
export interface BuyCart {
|
|
129
135
|
merchant: string;
|
|
130
136
|
merchant_name: string;
|
|
131
137
|
store: BuyStore | null;
|
|
132
138
|
items: BuyCartLine[];
|
|
139
|
+
/** The items alone, or null from an older API. */
|
|
140
|
+
subtotalCents: number | null;
|
|
133
141
|
serviceFeesCents: number;
|
|
142
|
+
/** The store's own fee and tax lines inside `serviceFeesCents`, or null when the store itemized none. */
|
|
143
|
+
merchantFeeLines: BuyCartFeeLine[] | null;
|
|
134
144
|
tipCents: number;
|
|
135
145
|
totalCents: number;
|
|
146
|
+
/**
|
|
147
|
+
* True when the store has not priced the cart's fees and tax yet, so `totalCents` covers the items and
|
|
148
|
+
* known fees only: show it as an estimate. DoorDash prices a cart once the shopper's own account holds it.
|
|
149
|
+
*/
|
|
150
|
+
totalIsEstimate: boolean;
|
|
136
151
|
/** The ISO 4217 code the store prices the cart in, or null when Agentcard sent none. */
|
|
137
152
|
currency: string | null;
|
|
138
153
|
approvedCeilingCents: number | null;
|
|
@@ -184,13 +199,15 @@ export interface BuyPick {
|
|
|
184
199
|
/**
|
|
185
200
|
* One store a delivery store search found that the answer's reply names. `id` holds no link and is what
|
|
186
201
|
* `pick.store_id` takes; `store_page_url` is the store's page at the merchant. `eta_minutes` is the
|
|
187
|
-
* merchant's delivery estimate, or null when it showed none.
|
|
202
|
+
* merchant's delivery estimate, or null when it showed none. `image_url` is the picture the store's
|
|
203
|
+
* card shows at the merchant, or null when it shows none.
|
|
188
204
|
*/
|
|
189
205
|
export interface BuyStoreChoice {
|
|
190
206
|
id: string;
|
|
191
207
|
name: string;
|
|
192
208
|
store_page_url: string | null;
|
|
193
209
|
eta_minutes: number | null;
|
|
210
|
+
image_url: string | null;
|
|
194
211
|
}
|
|
195
212
|
/** The stores an answer's reply presents to choose from, at most three, in the order the reply names them. */
|
|
196
213
|
export interface BuyStores {
|
|
@@ -213,11 +230,12 @@ export interface BuyPaymentSource {
|
|
|
213
230
|
/**
|
|
214
231
|
* The one thing the shopper does next, as Agentcard names it, in the order a purchase asks for them:
|
|
215
232
|
* `shipping_details` (a shipping address and phone), `guest_details` (a name, email and phone for a
|
|
216
|
-
* booking), `
|
|
233
|
+
* booking), `store_login` (a login to their own account at a store that checks out only on it, with
|
|
234
|
+
* the `store_login` link), `add_card` (a card in their Vault), `confirm` (their yes to the cart), `verification_code`
|
|
217
235
|
* (the code a booking texted them), then `approve` (the approval link). A value this version does
|
|
218
236
|
* not know reads as null.
|
|
219
237
|
*/
|
|
220
|
-
export type BuyNextAction = 'shipping_details' | 'guest_details' | 'add_card' | 'confirm' | 'verification_code' | 'approve';
|
|
238
|
+
export type BuyNextAction = 'shipping_details' | 'guest_details' | 'store_login' | 'add_card' | 'confirm' | 'verification_code' | 'approve';
|
|
221
239
|
/** One turn's answer, every field read for its type; a field an older API does not send reads as null. */
|
|
222
240
|
export interface BuyTurn {
|
|
223
241
|
conversation_id: string;
|
|
@@ -333,6 +351,9 @@ export declare class AgentcardClient {
|
|
|
333
351
|
private token;
|
|
334
352
|
private inflight;
|
|
335
353
|
private learnedSandbox;
|
|
354
|
+
/** Each shopper's messages that no history the model ran with holds yet, under your own id for them. */
|
|
355
|
+
private readonly inbox;
|
|
356
|
+
private readonly deliveredIds;
|
|
336
357
|
constructor(options: AgentcardClientOptions);
|
|
337
358
|
/** True when the client buys for many shoppers with a company's credentials; false under one shopper's own token. */
|
|
338
359
|
get company(): boolean;
|
|
@@ -365,6 +386,21 @@ export declare class AgentcardClient {
|
|
|
365
386
|
* webhook endpoint: `listenToAgentcard(this, opts)`. Takes your company credentials.
|
|
366
387
|
*/
|
|
367
388
|
listen(opts: ListenOptions): AgentcardListener;
|
|
389
|
+
/**
|
|
390
|
+
* Notes that `message` reached the chat of the shopper you call `externalUserId`, so that shopper's
|
|
391
|
+
* next `buy.turn` hands it to your model (`turn.messages`), until a history the model ran with holds it. `listen` and `agentcardWebhook({ agentcard })`
|
|
392
|
+
* call it once `onMessage` returns, so call it yourself only for a message you delivered another way.
|
|
393
|
+
* A message is kept once per event id. The messages live in this process's memory, at most 20 per
|
|
394
|
+
* shopper: the listener and the buy tool share one client in one process.
|
|
395
|
+
*/
|
|
396
|
+
delivered(externalUserId: string, message: ShopperMessage): void;
|
|
397
|
+
/**
|
|
398
|
+
* The messages kept for this shopper, oldest first: each one that reached their chat and that no
|
|
399
|
+
* history `turn.messages` read has held yet. `buy.turn` reads them when it opens.
|
|
400
|
+
*/
|
|
401
|
+
keptMessages(externalUserId: string): ShopperMessage[];
|
|
402
|
+
/** Forgets these messages for this shopper: `turn.messages` calls it once the history it reads holds them. */
|
|
403
|
+
forgetMessages(externalUserId: string, messages: readonly ShopperMessage[]): void;
|
|
368
404
|
/** A company names the shopper exactly once; a shopper's own token already names them. */
|
|
369
405
|
private checkShopper;
|
|
370
406
|
private call;
|
package/dist/client.js
CHANGED
|
@@ -18,7 +18,13 @@ export const BUY_TURN_TIMEOUT_MS = 130_000;
|
|
|
18
18
|
const CALL_TIMEOUT_MS = 30_000;
|
|
19
19
|
/** A token is renewed this long before it expires, so it never expires mid-call. */
|
|
20
20
|
const RENEW_EARLY_MS = 60_000;
|
|
21
|
-
|
|
21
|
+
/** The messages a shopper's chat got that the client keeps for that shopper's next turn, the oldest dropped first. */
|
|
22
|
+
const DELIVERED_PER_SHOPPER = 20;
|
|
23
|
+
/** The shoppers the client keeps such messages for, the one written to longest ago dropped first. */
|
|
24
|
+
const DELIVERED_SHOPPERS = 10_000;
|
|
25
|
+
/** The event ids the client remembers taking in, so a message delivered twice reaches the model once. */
|
|
26
|
+
const DELIVERED_IDS_KEPT = 10_000;
|
|
27
|
+
const NEXT_ACTIONS = new Set(['shipping_details', 'guest_details', 'store_login', 'add_card', 'confirm', 'verification_code', 'approve']);
|
|
22
28
|
/**
|
|
23
29
|
* A refusal, or a failure to reach Agentcard. `code` is the API's own
|
|
24
30
|
* (`user_not_connected`, `external_user_id_ambiguous`, `turn_in_progress`,
|
|
@@ -75,6 +81,12 @@ function storeOf(v) {
|
|
|
75
81
|
return null;
|
|
76
82
|
return { id: v.id, ...(str(v.name) ? { name: v.name } : {}) };
|
|
77
83
|
}
|
|
84
|
+
function feeLinesOf(v) {
|
|
85
|
+
if (!Array.isArray(v))
|
|
86
|
+
return null;
|
|
87
|
+
const lines = v.filter(isRecord).flatMap((f) => (str(f.label) && num(f.amountCents) !== null ? [{ label: f.label, amountCents: f.amountCents }] : []));
|
|
88
|
+
return lines.length ? lines : null;
|
|
89
|
+
}
|
|
78
90
|
function cartOf(v) {
|
|
79
91
|
if (!isRecord(v) || !str(v.hash) || !str(v.merchant))
|
|
80
92
|
return null;
|
|
@@ -89,9 +101,12 @@ function cartOf(v) {
|
|
|
89
101
|
product_id: str(i.product_id),
|
|
90
102
|
url: pageUrl(i.url),
|
|
91
103
|
})) : [],
|
|
104
|
+
subtotalCents: num(v.subtotalCents),
|
|
92
105
|
serviceFeesCents: num(v.serviceFeesCents) ?? 0,
|
|
106
|
+
merchantFeeLines: feeLinesOf(v.merchantFeeLines),
|
|
93
107
|
tipCents: num(v.tipCents) ?? 0,
|
|
94
108
|
totalCents: num(v.totalCents) ?? 0,
|
|
109
|
+
totalIsEstimate: v.totalIsEstimate === true,
|
|
95
110
|
currency: str(v.currency),
|
|
96
111
|
approvedCeilingCents: num(v.approvedCeilingCents),
|
|
97
112
|
hash: v.hash,
|
|
@@ -140,7 +155,7 @@ function storesOf(v) {
|
|
|
140
155
|
const id = str(i.id);
|
|
141
156
|
const name = str(i.name);
|
|
142
157
|
const eta = num(i.eta_minutes);
|
|
143
|
-
return id && name ? [{ id, name, store_page_url: pageUrl(i.store_page_url), eta_minutes: eta }] : [];
|
|
158
|
+
return id && name ? [{ id, name, store_page_url: pageUrl(i.store_page_url), eta_minutes: eta, image_url: str(i.image_url) }] : [];
|
|
144
159
|
});
|
|
145
160
|
return items.length ? { merchant: str(v.merchant) ?? '', merchant_name: str(v.merchant_name) ?? str(v.merchant) ?? '', items, as_of: str(v.as_of) } : null;
|
|
146
161
|
}
|
|
@@ -380,6 +395,9 @@ export class AgentcardClient {
|
|
|
380
395
|
token = null;
|
|
381
396
|
inflight = null;
|
|
382
397
|
learnedSandbox = null;
|
|
398
|
+
/** Each shopper's messages that no history the model ran with holds yet, under your own id for them. */
|
|
399
|
+
inbox = new Map();
|
|
400
|
+
deliveredIds = new Set();
|
|
383
401
|
constructor(options) {
|
|
384
402
|
const o = (options ?? {});
|
|
385
403
|
const hasCredentials = o.clientId !== undefined || o.clientSecret !== undefined;
|
|
@@ -470,6 +488,52 @@ export class AgentcardClient {
|
|
|
470
488
|
listen(opts) {
|
|
471
489
|
return listenToAgentcard(this, opts);
|
|
472
490
|
}
|
|
491
|
+
/**
|
|
492
|
+
* Notes that `message` reached the chat of the shopper you call `externalUserId`, so that shopper's
|
|
493
|
+
* next `buy.turn` hands it to your model (`turn.messages`), until a history the model ran with holds it. `listen` and `agentcardWebhook({ agentcard })`
|
|
494
|
+
* call it once `onMessage` returns, so call it yourself only for a message you delivered another way.
|
|
495
|
+
* A message is kept once per event id. The messages live in this process's memory, at most 20 per
|
|
496
|
+
* shopper: the listener and the buy tool share one client in one process.
|
|
497
|
+
*/
|
|
498
|
+
delivered(externalUserId, message) {
|
|
499
|
+
if (!str(externalUserId))
|
|
500
|
+
throw new TypeError('Pass externalUserId: your own id for the shopper whose chat got the message.');
|
|
501
|
+
if (!isRecord(message) || typeof message.text !== 'string')
|
|
502
|
+
throw new TypeError('Pass the message onMessage got, with its text.');
|
|
503
|
+
if (message.text.trim() === '')
|
|
504
|
+
return;
|
|
505
|
+
if (str(message.eventId)) {
|
|
506
|
+
if (this.deliveredIds.has(message.eventId))
|
|
507
|
+
return;
|
|
508
|
+
this.deliveredIds.add(message.eventId);
|
|
509
|
+
if (this.deliveredIds.size > DELIVERED_IDS_KEPT)
|
|
510
|
+
this.deliveredIds.delete(this.deliveredIds.values().next().value);
|
|
511
|
+
}
|
|
512
|
+
const kept = this.inbox.get(externalUserId) ?? [];
|
|
513
|
+
// The shopper written to last moves to the end, so the one dropped first is the one written to longest ago.
|
|
514
|
+
this.inbox.delete(externalUserId);
|
|
515
|
+
this.inbox.set(externalUserId, [...kept, message].slice(-DELIVERED_PER_SHOPPER));
|
|
516
|
+
if (this.inbox.size > DELIVERED_SHOPPERS)
|
|
517
|
+
this.inbox.delete(this.inbox.keys().next().value);
|
|
518
|
+
}
|
|
519
|
+
/**
|
|
520
|
+
* The messages kept for this shopper, oldest first: each one that reached their chat and that no
|
|
521
|
+
* history `turn.messages` read has held yet. `buy.turn` reads them when it opens.
|
|
522
|
+
*/
|
|
523
|
+
keptMessages(externalUserId) {
|
|
524
|
+
return [...(this.inbox.get(externalUserId) ?? [])];
|
|
525
|
+
}
|
|
526
|
+
/** Forgets these messages for this shopper: `turn.messages` calls it once the history it reads holds them. */
|
|
527
|
+
forgetMessages(externalUserId, messages) {
|
|
528
|
+
const kept = this.inbox.get(externalUserId);
|
|
529
|
+
if (!kept)
|
|
530
|
+
return;
|
|
531
|
+
const left = kept.filter((m) => !messages.includes(m));
|
|
532
|
+
if (left.length > 0)
|
|
533
|
+
this.inbox.set(externalUserId, left);
|
|
534
|
+
else
|
|
535
|
+
this.inbox.delete(externalUserId);
|
|
536
|
+
}
|
|
473
537
|
/** A company names the shopper exactly once; a shopper's own token already names them. */
|
|
474
538
|
checkShopper(who) {
|
|
475
539
|
const named = [who.externalUserId, who.userId].filter((v) => v !== undefined);
|
package/dist/definition.js
CHANGED
|
@@ -10,23 +10,27 @@ export const BUY_TOOL_NAME = 'buy';
|
|
|
10
10
|
export const BUY_TOOL_DESCRIPTION = [
|
|
11
11
|
'Buy what this shopper asks for at real stores.',
|
|
12
12
|
"Put the shopper's own words in `ask`.",
|
|
13
|
-
'Tell the shopper what `reply` says, in your own voice.',
|
|
13
|
+
'Tell the shopper what `reply` says, in your own voice, except the products and stores it lists that reach the shopper as cards.',
|
|
14
14
|
"When `shopper_already_told_looking` is true, the shopper was already told you are working on their request, so never acknowledge it again: open with the answer or the question.",
|
|
15
15
|
'Show the shopper the cart before you confirm it.',
|
|
16
16
|
'Ask the shopper for exactly one thing per message: the step `next_action` names, even when `reply` lists more. Never mention a later step, and never ask for the next one until the shopper has done this one.',
|
|
17
|
-
'
|
|
17
|
+
'When `next_action` is null and `reply` is about something other than a cart, ask for no checkout step: a cart from an earlier request gets one short line at most, after the answer to what the shopper just asked, and never a question about confirming or placing it.',
|
|
18
|
+
'Set `confirm` to true only when the shopper just said yes to the cart in your last result, asks to check out or pay for a cart that waits, says they added the card for a cart that waits, says they approved, or asks for the approval link again.',
|
|
19
|
+
'When the shopper asks to check out, pay or place the order, call this tool: Agentcard knows whether their card is saved, so never ask the shopper whether they added it.',
|
|
18
20
|
'Never write a link.',
|
|
19
21
|
'Each link reaches the shopper on its own.',
|
|
20
22
|
'When `link_sent` is true, tell the shopper a link is waiting for them.',
|
|
21
|
-
'Product links reach the shopper on their own,
|
|
23
|
+
'Product links reach the shopper on their own, as cards that show each product\'s name, price and picture. When `product_links_sent` is above zero, the shopper already sees those products, so never list them again, by name, price or number, even when `reply` lists them: write one short line, such as which one they want, plus only what the cards do not show, such as why one is the pick. When the shopper asks for links, tell them the links are there; never write or invent a URL.',
|
|
22
24
|
'A `placing` status means the order is going through, so never ask for another approval.',
|
|
23
25
|
'Tell the shopper they need not reply only when `shopper_need_not_reply` is true.',
|
|
24
26
|
'After `needs_card` with a cart, Agentcard keeps the cart, so set `confirm` to true once the shopper says they added the card.',
|
|
25
27
|
"After `needs_card` with no cart, call this tool again with the shopper's request once they say they added a card.",
|
|
26
28
|
'Set `new_order` to true only when the shopper drops the cart.',
|
|
29
|
+
"Never tell the shopper that you or Agentcard can't see their name, phone number or address: Agentcard may use the ones saved on their Agentcard account for an order, and `reply` says when it does.",
|
|
27
30
|
"When the shopper picks one of the `products` in your last result, by its name, its place in the list or a tap that names it, set `product_id` to that product's `product_id`, so Agentcard adds exactly that product; otherwise set it to null.",
|
|
28
31
|
"When the shopper picks one of the `stores` in your last result, by its name, its place in the list or a tap that names it, set `store_id` to that store's `store_id`, so Agentcard orders from exactly that store; otherwise set it to null. Never set both `product_id` and `store_id`.",
|
|
29
|
-
'Store links reach the shopper on their own,
|
|
32
|
+
'Store links reach the shopper on their own, as cards that show each store\'s name and delivery time. When `store_links_sent` is above zero, never list those stores again: ask which one in one short line.',
|
|
33
|
+
'When `store_links_sent` is 0 and you offer the `stores`, name each one by its `name`, never only "their links are there".',
|
|
30
34
|
'When `pick.in_cart` is false, the product the shopper picked is not in the cart: tell the shopper what `reply` says, and never ask them to confirm the cart.',
|
|
31
35
|
].join(' ');
|
|
32
36
|
/** A fresh copy of the tool's definition, so a caller that edits one never changes another. */
|
|
@@ -40,7 +44,7 @@ export function buyToolDefinition() {
|
|
|
40
44
|
ask: { type: 'string', description: 'What the shopper said, in their own words.' },
|
|
41
45
|
confirm: {
|
|
42
46
|
type: 'boolean',
|
|
43
|
-
description: 'True only when the shopper just said yes to the cart in your last result, says they added the card for a cart that waits, says they approved, or asks for the approval link again. False otherwise.',
|
|
47
|
+
description: 'True only when the shopper just said yes to the cart in your last result, asks to check out or pay for a cart that waits, says they added the card for a cart that waits, says they approved, or asks for the approval link again. False otherwise.',
|
|
44
48
|
},
|
|
45
49
|
new_order: { type: 'boolean', description: 'True only when the shopper drops the cart to start a new order. False otherwise.' },
|
|
46
50
|
product_id: {
|
package/dist/handler.d.ts
CHANGED
|
@@ -7,6 +7,7 @@
|
|
|
7
7
|
*/
|
|
8
8
|
import { type AgentcardEvent, type VerifyOptions } from './verify.js';
|
|
9
9
|
import { type PurchaseMessage } from './message.js';
|
|
10
|
+
import type { AgentcardClient } from './client.js';
|
|
10
11
|
import { type ShopperMessage } from './shopper-message.js';
|
|
11
12
|
/** The parts of a Node or Express request the handler reads. */
|
|
12
13
|
export interface WebhookRequest {
|
|
@@ -80,6 +81,11 @@ export interface AgentcardWebhookOptions extends PurchaseAgentWebhookOptions {
|
|
|
80
81
|
* such as `vault.card_stored`, and a shopper message for a shopper you gave Agentcard no id for.
|
|
81
82
|
*/
|
|
82
83
|
onEvent?: (event: AgentcardEvent) => void | Promise<void>;
|
|
84
|
+
/**
|
|
85
|
+
* The client your buy tools share. Once `onMessage` returns, the client keeps the message for that
|
|
86
|
+
* shopper's next `buy.turn`, which hands it to your model, as `agentcard.listen` does.
|
|
87
|
+
*/
|
|
88
|
+
agentcard?: AgentcardClient;
|
|
83
89
|
}
|
|
84
90
|
/** The handler `agentcardWebhook` returns: a Node or Express `(req, res)` handler, with `fetch` for a web `Request`. */
|
|
85
91
|
export interface AgentcardWebhookHandler {
|
package/dist/handler.js
CHANGED
|
@@ -284,6 +284,9 @@ export function agentcardWebhook(opts) {
|
|
|
284
284
|
if (typeof opts.onMessage !== 'function') {
|
|
285
285
|
throw new TypeError('Pass onMessage, the function that receives each message for a shopper.');
|
|
286
286
|
}
|
|
287
|
+
if (opts.agentcard !== undefined && typeof opts.agentcard?.delivered !== 'function') {
|
|
288
|
+
throw new TypeError('Pass agentcard as the AgentcardClient your buy tools share, or leave it out.');
|
|
289
|
+
}
|
|
287
290
|
const secret = opts.secret;
|
|
288
291
|
const verifyOpts = { toleranceSeconds: toleranceOf(opts) };
|
|
289
292
|
const report = reporter(opts.onError);
|
|
@@ -316,6 +319,7 @@ export function agentcardWebhook(opts) {
|
|
|
316
319
|
try {
|
|
317
320
|
if (message?.externalUserId) {
|
|
318
321
|
await opts.onMessage(message.externalUserId, message, event);
|
|
322
|
+
opts.agentcard?.delivered(message.externalUserId, message);
|
|
319
323
|
}
|
|
320
324
|
else if (opts.onEvent) {
|
|
321
325
|
await opts.onEvent(event);
|
package/dist/index.d.ts
CHANGED
|
@@ -2,10 +2,10 @@ export { verifyAgentcardWebhook, AgentcardSignatureError, type AgentcardSignatur
|
|
|
2
2
|
export { readPurchaseMessage, type PurchaseMessage, type PurchaseMessageKind, type PurchaseFacts, } from './message.js';
|
|
3
3
|
export { readShopperMessage, isStale, SHOPPER_EVENT_TYPES, type ShopperMessage, type ShopperMessageKind, type ShopperFacts, type ShopperEventType, type OrderStatus, } from './shopper-message.js';
|
|
4
4
|
export { agentcardWebhook, purchaseAgentWebhook, type AgentcardWebhookOptions, type AgentcardWebhookHandler, type PurchaseAgentWebhookOptions, type WebhookFetchRequest, type WebhookRequest, type WebhookResponse, } from './handler.js';
|
|
5
|
-
export { AgentcardClient, AgentcardApiError, BUY_TURN_TIMEOUT_MS, DEFAULT_BASE_URL, type AgentcardClientOptions, type FetchLike, type FetchLikeResponse, type BuyProgress, type BuyProgressStage, type BuyCatalogUpdate, type BuyStreamHandlers, type Shopper, type BuyRequest, type BuyDeliveryAddress, type BuyTurn, type BuyStatus, type BuyCart, type BuyCartLine, type BuyStore, type BuyCatalog, type BuyCatalogItem, type BuyFlightItinerary, type BuyPaymentSource, type BuyConversation, type BuyLastCheckout, type BuyOrder, type VaultLink, type BuyNextAction, type BuyPick, type BuyStoreChoice, type BuyStorePick, type BuyStores, } from './client.js';
|
|
5
|
+
export { AgentcardClient, AgentcardApiError, BUY_TURN_TIMEOUT_MS, DEFAULT_BASE_URL, type AgentcardClientOptions, type FetchLike, type FetchLikeResponse, type BuyProgress, type BuyProgressStage, type BuyCatalogUpdate, type BuyStreamHandlers, type Shopper, type BuyRequest, type BuyDeliveryAddress, type BuyTurn, type BuyStatus, type BuyCart, type BuyCartLine, type BuyCartFeeLine, type BuyStore, type BuyCatalog, type BuyCatalogItem, type BuyFlightItinerary, type BuyPaymentSource, type BuyConversation, type BuyLastCheckout, type BuyOrder, type VaultLink, type BuyNextAction, type BuyPick, type BuyStoreChoice, type BuyStorePick, type BuyStores, } from './client.js';
|
|
6
6
|
export { BUY_TOOL_NAME, BUY_TOOL_DESCRIPTION, buyToolDefinition, parseBuyToolInput, type BuyToolDefinition, type BuyToolInput, type BuyToolInputSchema, type BuyToolInputCheck, type BuyToolStandardSchema, type OpenAIBuyTool, } from './definition.js';
|
|
7
|
-
export { toMessages, messageStream, type MessageStream, type MessageStreamOptions, } from './messages.js';
|
|
8
|
-
export { buyTool, type BuyTool, type BuyToolOptions, type BuyToolResult, type BuyToolStatus, type BuyToolLink, type BuyToolProduct, type BuyToolStore, } from './buy-tool.js';
|
|
7
|
+
export { toMessages, messageStream, typingDelay, type MessagePace, type MessageStream, type MessageStreamOptions, } from './messages.js';
|
|
8
|
+
export { buyTool, type BuyTool, type BuyToolOptions, type BuyToolResult, type BuyToolStatus, type BuyToolLink, type BuyToolProduct, type BuyToolStore, type BuyToolTurn, type BuyToolTurnItem, type BuyToolTurnOptions, type BuyToolTurnMessage, } from './buy-tool.js';
|
|
9
9
|
export { instantAck, ackFor, type AckForOptions } from './ack.js';
|
|
10
10
|
export { forModel, forModelError, stripLinks, type BuyToolModelView, type BuyToolModelCart, type BuyToolModelProduct, type BuyToolModelStore, } from './model-view.js';
|
|
11
11
|
export { toAnthropicTool, toOpenRouterTool, type AnthropicBuyTool, type OpenRouterToolkit, type ZodLike, } from './adapters.js';
|
package/dist/index.js
CHANGED
|
@@ -4,7 +4,7 @@ export { readShopperMessage, isStale, SHOPPER_EVENT_TYPES, } from './shopper-mes
|
|
|
4
4
|
export { agentcardWebhook, purchaseAgentWebhook, } from './handler.js';
|
|
5
5
|
export { AgentcardClient, AgentcardApiError, BUY_TURN_TIMEOUT_MS, DEFAULT_BASE_URL, } from './client.js';
|
|
6
6
|
export { BUY_TOOL_NAME, BUY_TOOL_DESCRIPTION, buyToolDefinition, parseBuyToolInput, } from './definition.js';
|
|
7
|
-
export { toMessages, messageStream, } from './messages.js';
|
|
7
|
+
export { toMessages, messageStream, typingDelay, } from './messages.js';
|
|
8
8
|
export { buyTool, } from './buy-tool.js';
|
|
9
9
|
export { instantAck, ackFor } from './ack.js';
|
|
10
10
|
export { forModel, forModelError, stripLinks, } from './model-view.js';
|
package/dist/listen.d.ts
CHANGED
|
@@ -47,6 +47,8 @@ export interface ListenOptions {
|
|
|
47
47
|
* Receives each event for one of your shoppers, under your own id for them, with the message
|
|
48
48
|
* ready to post into that shopper's chat: the same as `agentcardWebhook`'s `onMessage`. A message
|
|
49
49
|
* whose link or window closed before it arrived reaches no shopper: its event goes to `onEvent`.
|
|
50
|
+
* Once it returns, the client keeps the message for that shopper's next `buy.turn`, which hands it
|
|
51
|
+
* to your model, so the model knows what the shopper read between turns.
|
|
50
52
|
*/
|
|
51
53
|
onMessage?: (externalUserId: string, message: ShopperMessage, event: AgentcardEvent<AgentcardEventType>) => void | Promise<void>;
|
|
52
54
|
/** Receives every event `onMessage` does not take: every event, when you pass no `onMessage`. */
|
package/dist/listen.js
CHANGED
|
@@ -198,8 +198,11 @@ export function listenToAgentcard(agentcard, opts) {
|
|
|
198
198
|
const forShopper = message && message.externalUserId !== null && !isStale(message) ? message : null;
|
|
199
199
|
for (let attempt = 1;; attempt++) {
|
|
200
200
|
try {
|
|
201
|
-
if (opts.onMessage && forShopper?.externalUserId)
|
|
201
|
+
if (opts.onMessage && forShopper?.externalUserId) {
|
|
202
202
|
await opts.onMessage(forShopper.externalUserId, forShopper, event);
|
|
203
|
+
// The shopper's chat holds the message now, so their next `buy.turn` tells the model.
|
|
204
|
+
agentcard.delivered(forShopper.externalUserId, forShopper);
|
|
205
|
+
}
|
|
203
206
|
else if (opts.onEvent)
|
|
204
207
|
await opts.onEvent(event);
|
|
205
208
|
return finish(event.id);
|
package/dist/messages.d.ts
CHANGED
|
@@ -14,6 +14,23 @@
|
|
|
14
14
|
* ```
|
|
15
15
|
*/
|
|
16
16
|
export declare function toMessages(text: string): string[];
|
|
17
|
+
/**
|
|
18
|
+
* How long someone texting takes to type `message`, in milliseconds: 300, plus 12 a character, at most
|
|
19
|
+
* 1,500. `messageStream` and `buy.turn` wait this long between two messages by default, so a model that
|
|
20
|
+
* writes three paragraphs in 100 ms still delivers them one at a time.
|
|
21
|
+
*
|
|
22
|
+
* ```ts
|
|
23
|
+
* typingDelay('which one do you want?'); // 564
|
|
24
|
+
* ```
|
|
25
|
+
*/
|
|
26
|
+
export declare function typingDelay(message: string): number;
|
|
27
|
+
/** How messages are spaced: `true` by `typingDelay`, `false` not at all, or a function that returns the pause for a message. */
|
|
28
|
+
export type MessagePace = boolean | ((message: string) => number);
|
|
29
|
+
/** The pause for each message `pace` asks for, or null for none. Throws a TypeError for a pace that cannot work. */
|
|
30
|
+
export declare function paceOf(pace: unknown): ((message: string) => number) | null;
|
|
31
|
+
/** A typing hook that never throws: one that fails costs nothing but the indicator. */
|
|
32
|
+
export declare function typingHook(onTyping: unknown): (typing: boolean) => void;
|
|
33
|
+
export declare const sleep: (ms: number) => Promise<void>;
|
|
17
34
|
/** What `messageStream` hands back: push each piece of text as it arrives, then end once it stops. */
|
|
18
35
|
export interface MessageStream {
|
|
19
36
|
/** Adds the next piece of text. Each message the piece completes goes to `onMessage` in order, after the one before it settled. */
|
|
@@ -31,10 +48,24 @@ export interface MessageStreamOptions {
|
|
|
31
48
|
* After a send fails, no later message is sent, and `end` rejects with the failure.
|
|
32
49
|
*/
|
|
33
50
|
onMessage: (message: string) => void | Promise<void>;
|
|
51
|
+
/**
|
|
52
|
+
* Spaces the messages like someone texting. Each message after the first waits until `typingDelay`
|
|
53
|
+
* of it has passed since the one before it went out, so a model that writes three paragraphs in
|
|
54
|
+
* 100 ms still delivers them one at a time. Time the model spent writing counts toward the pause, so
|
|
55
|
+
* a slow model gets no added wait. Default `true`. `false` hands each message over the moment it is
|
|
56
|
+
* written, and a function returns the pause in milliseconds for a message.
|
|
57
|
+
*/
|
|
58
|
+
pace?: MessagePace;
|
|
59
|
+
/**
|
|
60
|
+
* Hears `true` when the pause before a message starts and `false` the moment that message goes out,
|
|
61
|
+
* so your app can show a typing indicator between messages. One that throws costs nothing.
|
|
62
|
+
*/
|
|
63
|
+
onTyping?: (typing: boolean) => void;
|
|
34
64
|
}
|
|
35
65
|
/**
|
|
36
|
-
* Cuts streamed text into the messages `toMessages` finds, as it arrives. Each paragraph
|
|
37
|
-
*
|
|
66
|
+
* Cuts streamed text into the messages `toMessages` finds, as it arrives. Each paragraph is ready the
|
|
67
|
+
* moment the blank line that ends it arrives, whatever the pieces split it into, and goes to
|
|
68
|
+
* `onMessage` then, or, after the first, once its typing pause since the last one passed (`pace`). A
|
|
38
69
|
* paragraph inside a list waits for the first characters of the line after the blank, which say
|
|
39
70
|
* whether the list goes on. `end` hands over the last message and waits for every send.
|
|
40
71
|
*
|
package/dist/messages.js
CHANGED
|
@@ -116,9 +116,60 @@ export function toMessages(text) {
|
|
|
116
116
|
scanner.end();
|
|
117
117
|
return messages;
|
|
118
118
|
}
|
|
119
|
+
/** The pause before a message, as someone texting takes it: 300 ms, plus 12 ms a character, at most 1.5 seconds. */
|
|
120
|
+
const TYPING_BASE_MS = 300;
|
|
121
|
+
const TYPING_MS_PER_CHARACTER = 12;
|
|
122
|
+
const TYPING_MAX_MS = 1_500;
|
|
119
123
|
/**
|
|
120
|
-
*
|
|
121
|
-
* `
|
|
124
|
+
* How long someone texting takes to type `message`, in milliseconds: 300, plus 12 a character, at most
|
|
125
|
+
* 1,500. `messageStream` and `buy.turn` wait this long between two messages by default, so a model that
|
|
126
|
+
* writes three paragraphs in 100 ms still delivers them one at a time.
|
|
127
|
+
*
|
|
128
|
+
* ```ts
|
|
129
|
+
* typingDelay('which one do you want?'); // 564
|
|
130
|
+
* ```
|
|
131
|
+
*/
|
|
132
|
+
export function typingDelay(message) {
|
|
133
|
+
return Math.min(TYPING_MAX_MS, TYPING_BASE_MS + TYPING_MS_PER_CHARACTER * [...String(message ?? '')].length);
|
|
134
|
+
}
|
|
135
|
+
/** The pause for each message `pace` asks for, or null for none. Throws a TypeError for a pace that cannot work. */
|
|
136
|
+
export function paceOf(pace) {
|
|
137
|
+
if (pace === undefined || pace === true)
|
|
138
|
+
return typingDelay;
|
|
139
|
+
if (pace === false)
|
|
140
|
+
return null;
|
|
141
|
+
if (typeof pace !== 'function')
|
|
142
|
+
throw new TypeError('Pass pace as true, false, or a function that returns the pause before a message in milliseconds.');
|
|
143
|
+
return (message) => {
|
|
144
|
+
let ms;
|
|
145
|
+
try {
|
|
146
|
+
ms = Number(pace(message));
|
|
147
|
+
}
|
|
148
|
+
catch {
|
|
149
|
+
// A pace function that fails costs only the pause.
|
|
150
|
+
return 0;
|
|
151
|
+
}
|
|
152
|
+
return Number.isFinite(ms) && ms > 0 ? ms : 0;
|
|
153
|
+
};
|
|
154
|
+
}
|
|
155
|
+
/** A typing hook that never throws: one that fails costs nothing but the indicator. */
|
|
156
|
+
export function typingHook(onTyping) {
|
|
157
|
+
if (onTyping !== undefined && typeof onTyping !== 'function')
|
|
158
|
+
throw new TypeError('Pass onTyping as a function that takes true when a pause starts and false when its message goes out.');
|
|
159
|
+
return (typing) => {
|
|
160
|
+
try {
|
|
161
|
+
onTyping?.(typing);
|
|
162
|
+
}
|
|
163
|
+
catch {
|
|
164
|
+
// The app's indicator failing never stops a message.
|
|
165
|
+
}
|
|
166
|
+
};
|
|
167
|
+
}
|
|
168
|
+
export const sleep = (ms) => new Promise((resolve) => setTimeout(resolve, ms));
|
|
169
|
+
/**
|
|
170
|
+
* Cuts streamed text into the messages `toMessages` finds, as it arrives. Each paragraph is ready the
|
|
171
|
+
* moment the blank line that ends it arrives, whatever the pieces split it into, and goes to
|
|
172
|
+
* `onMessage` then, or, after the first, once its typing pause since the last one passed (`pace`). A
|
|
122
173
|
* paragraph inside a list waits for the first characters of the line after the blank, which say
|
|
123
174
|
* whether the list goes on. `end` hands over the last message and waits for every send.
|
|
124
175
|
*
|
|
@@ -133,16 +184,38 @@ export function messageStream(options) {
|
|
|
133
184
|
const onMessage = options?.onMessage;
|
|
134
185
|
if (typeof onMessage !== 'function')
|
|
135
186
|
throw new TypeError('Pass onMessage as a function that takes each message.');
|
|
187
|
+
const pauseOf = paceOf(options.pace);
|
|
188
|
+
const typing = typingHook(options.onTyping);
|
|
136
189
|
// The line still being written. A trailing \r waits here too, in case its \n is in the next piece.
|
|
137
190
|
let open = '';
|
|
138
191
|
let ended = false;
|
|
139
192
|
let failure = null;
|
|
140
|
-
// Messages waiting for the send before them. A send that returns no promise goes out at once.
|
|
193
|
+
// Messages waiting for the send before them, or for their pause. A send that returns no promise goes out at once.
|
|
141
194
|
const queue = [];
|
|
142
195
|
let running = null;
|
|
196
|
+
// When the last message went out, and whether the app was told a pause started.
|
|
197
|
+
let lastAt = null;
|
|
198
|
+
let paused = false;
|
|
199
|
+
// The pause chosen for the message at the head of the queue: chosen once, so a pace that varies never makes it wait twice.
|
|
200
|
+
let headPause = null;
|
|
143
201
|
const drain = () => {
|
|
144
202
|
while (queue.length > 0 && !failure) {
|
|
145
|
-
const message = queue
|
|
203
|
+
const message = queue[0];
|
|
204
|
+
if (pauseOf && lastAt !== null) {
|
|
205
|
+
headPause ??= pauseOf(message);
|
|
206
|
+
const wait = lastAt + headPause - Date.now();
|
|
207
|
+
if (wait > 0) {
|
|
208
|
+
if (!paused)
|
|
209
|
+
typing(true);
|
|
210
|
+
paused = true;
|
|
211
|
+
return sleep(wait).then(() => drain() ?? undefined);
|
|
212
|
+
}
|
|
213
|
+
}
|
|
214
|
+
queue.shift();
|
|
215
|
+
headPause = null;
|
|
216
|
+
if (paused)
|
|
217
|
+
typing(false);
|
|
218
|
+
paused = false;
|
|
146
219
|
let sent;
|
|
147
220
|
try {
|
|
148
221
|
sent = onMessage(message);
|
|
@@ -152,10 +225,14 @@ export function messageStream(options) {
|
|
|
152
225
|
break;
|
|
153
226
|
}
|
|
154
227
|
if (sent && typeof sent.then === 'function') {
|
|
155
|
-
return Promise.resolve(sent).then(() =>
|
|
228
|
+
return Promise.resolve(sent).then(() => {
|
|
229
|
+
lastAt = Date.now();
|
|
230
|
+
return drain() ?? undefined;
|
|
231
|
+
}, (error) => {
|
|
156
232
|
failure = { error };
|
|
157
233
|
});
|
|
158
234
|
}
|
|
235
|
+
lastAt = Date.now();
|
|
159
236
|
}
|
|
160
237
|
return null;
|
|
161
238
|
};
|
package/dist/model-view.d.ts
CHANGED
|
@@ -17,8 +17,17 @@ export interface BuyToolModelCart {
|
|
|
17
17
|
price: string | null;
|
|
18
18
|
}>;
|
|
19
19
|
fees: string | null;
|
|
20
|
+
/** The store's own fee and tax lines inside `fees`, by the store's labels. Empty when it itemized none. */
|
|
21
|
+
fee_lines: Array<{
|
|
22
|
+
label: string;
|
|
23
|
+
amount: string;
|
|
24
|
+
}>;
|
|
20
25
|
tip: string | null;
|
|
21
26
|
total: string;
|
|
27
|
+
/** True when the store has not priced its fees and tax yet: `total` is an estimate, never the final amount. */
|
|
28
|
+
total_is_estimate: boolean;
|
|
29
|
+
/** The most the shopper's card can be held for this cart: above `total` at a store that prices its final at checkout, `total` itself for a flight, which holds exactly that; else null. */
|
|
30
|
+
max_charge: string | null;
|
|
22
31
|
}
|
|
23
32
|
export interface BuyToolModelProduct {
|
|
24
33
|
/** What the model passes as `product_id` when the shopper picks this product: Agentcard's ref, never a link. Null when there is none. */
|
|
@@ -57,7 +66,7 @@ export interface BuyToolModelView {
|
|
|
57
66
|
shopper_already_told_looking: boolean;
|
|
58
67
|
/** True when the shopper already has the link this answer needed. */
|
|
59
68
|
link_sent: boolean;
|
|
60
|
-
/** How many
|
|
69
|
+
/** How many of the products above the shopper holds as cards, each with its name, price and picture; 0 when none went out. */
|
|
61
70
|
product_links_sent: number;
|
|
62
71
|
/** The product this call's `product_id` picked, and whether the cart now holds it; null when the call picked none. */
|
|
63
72
|
pick: {
|
|
@@ -102,6 +111,18 @@ export declare function shownStores(stores: readonly BuyToolStore[] | null | und
|
|
|
102
111
|
* it holds no link, else null. A retail product's id is its store page, and a link never reaches the model.
|
|
103
112
|
*/
|
|
104
113
|
export declare function pickId(product: Pick<BuyToolProduct, 'id' | 'ref'>): string | null;
|
|
114
|
+
/**
|
|
115
|
+
* What tells one product card from another: the product's link-free id, else its page. A restaurant gives
|
|
116
|
+
* every menu item the store's one page, so a card per page sent one card for a reply that offered three
|
|
117
|
+
* dishes (2026-10-20, Suavecito Birria & Tacos on DoorDash). Each product the model reads gets its own card.
|
|
118
|
+
*/
|
|
119
|
+
export declare function cardKey(product: Pick<BuyToolProduct, 'id' | 'ref' | 'url'>): string | null;
|
|
120
|
+
/**
|
|
121
|
+
* The key of the card a product gets, or null when it gets none: a product with a page at its store,
|
|
122
|
+
* or a flight option, which has no page, with an id a pick takes. A flight's card is how the shopper
|
|
123
|
+
* taps the flight they want (2026-10-20: three flights listed in text, and no card to tap).
|
|
124
|
+
*/
|
|
125
|
+
export declare function productCardKey(product: Pick<BuyToolProduct, 'id' | 'ref' | 'url' | 'flight'>): string | null;
|
|
105
126
|
/**
|
|
106
127
|
* The model's view of one answer: the status, Agentcard's reply, the newest
|
|
107
128
|
* cart and every cart on the table, the products by id, name and price, and
|
package/dist/model-view.js
CHANGED
|
@@ -109,13 +109,35 @@ export function pickId(product) {
|
|
|
109
109
|
return product.ref;
|
|
110
110
|
return typeof product.id === 'string' && product.id !== '' && stripLinks(product.id) === product.id ? product.id : null;
|
|
111
111
|
}
|
|
112
|
+
/**
|
|
113
|
+
* What tells one product card from another: the product's link-free id, else its page. A restaurant gives
|
|
114
|
+
* every menu item the store's one page, so a card per page sent one card for a reply that offered three
|
|
115
|
+
* dishes (2026-10-20, Suavecito Birria & Tacos on DoorDash). Each product the model reads gets its own card.
|
|
116
|
+
*/
|
|
117
|
+
export function cardKey(product) {
|
|
118
|
+
return pickId(product) ?? (typeof product.url === 'string' && product.url !== '' ? product.url : null);
|
|
119
|
+
}
|
|
120
|
+
/**
|
|
121
|
+
* The key of the card a product gets, or null when it gets none: a product with a page at its store,
|
|
122
|
+
* or a flight option, which has no page, with an id a pick takes. A flight's card is how the shopper
|
|
123
|
+
* taps the flight they want (2026-10-20: three flights listed in text, and no card to tap).
|
|
124
|
+
*/
|
|
125
|
+
export function productCardKey(product) {
|
|
126
|
+
if (typeof product.url === 'string' && product.url !== '')
|
|
127
|
+
return cardKey(product);
|
|
128
|
+
return product.flight ? pickId(product) : null;
|
|
129
|
+
}
|
|
112
130
|
function cartView(cart) {
|
|
113
131
|
return {
|
|
114
132
|
store: stripLinks(cart.store?.name ?? cart.merchant_name) || stripLinks(cart.merchant),
|
|
115
133
|
items: cart.items.map((line) => ({ name: stripLinks(line.name), qty: line.qty, price: money(line.priceCents, cart.currency) })),
|
|
116
134
|
fees: cart.serviceFeesCents > 0 ? money(cart.serviceFeesCents, cart.currency) : null,
|
|
135
|
+
fee_lines: (cart.merchantFeeLines ?? []).map((line) => ({ label: stripLinks(line.label), amount: money(line.amountCents, cart.currency) })),
|
|
117
136
|
tip: cart.tipCents > 0 ? money(cart.tipCents, cart.currency) : null,
|
|
118
137
|
total: money(cart.totalCents, cart.currency) ?? money(0, cart.currency),
|
|
138
|
+
total_is_estimate: cart.totalIsEstimate === true,
|
|
139
|
+
// Agentcard names a ceiling equal to the total only for a hold of exactly the total, as a flight's.
|
|
140
|
+
max_charge: typeof cart.approvedCeilingCents === 'number' && cart.approvedCeilingCents > 0 && cart.approvedCeilingCents >= cart.totalCents ? money(cart.approvedCeilingCents, cart.currency) : null,
|
|
119
141
|
};
|
|
120
142
|
}
|
|
121
143
|
/**
|
|
@@ -127,7 +149,7 @@ function cartView(cart) {
|
|
|
127
149
|
export function forModel(result) {
|
|
128
150
|
const status = typeof result.status === 'string' && MODEL_STATUSES.has(result.status) ? result.status : 'needs_input';
|
|
129
151
|
const products = shownProducts(result.products);
|
|
130
|
-
const held = (product) => product.linkSent === true &&
|
|
152
|
+
const held = (product) => product.linkSent === true && productCardKey(product) !== null;
|
|
131
153
|
const stores = shownStores(result.stores);
|
|
132
154
|
const heldStore = (store) => store.linkSent === true && typeof store.url === 'string' && store.url !== '';
|
|
133
155
|
return {
|
|
@@ -141,8 +163,8 @@ export function forModel(result) {
|
|
|
141
163
|
shopper_need_not_reply: result.shopper_need_not_reply === true,
|
|
142
164
|
shopper_already_told_looking: result.shopper_already_told_looking === true,
|
|
143
165
|
link_sent: result.link_sent === true,
|
|
144
|
-
//
|
|
145
|
-
product_links_sent: new Set(products.filter(held).map(
|
|
166
|
+
// A card per product, so two dishes on a restaurant's one page are two cards.
|
|
167
|
+
product_links_sent: new Set(products.filter(held).map(productCardKey)).size,
|
|
146
168
|
// The id the model sent, never the catalog id, which is a store page for a retail product.
|
|
147
169
|
pick: result.pick ? { product_id: pickId({ id: result.pick.product_id, ref: result.pick.ref }), in_cart: result.pick.in_cart === true } : null,
|
|
148
170
|
stores: stores.map((store) => ({ store_id: store.id, name: stripLinks(store.name), eta_minutes: store.etaMinutes, link_sent: heldStore(store) })),
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@agent-cards/commerce-sdk",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.10.0",
|
|
4
4
|
"description": "Give your agent one tool that buys for your shoppers at real stores, and post each purchase update Agentcard sends into the shopper's chat, over a connection your server opens or a webhook endpoint.",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"main": "dist/index.js",
|