@agent-cards/commerce-sdk 0.15.0 → 0.16.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 +52 -5
- package/dist/ack.d.ts +26 -16
- package/dist/ack.js +64 -2
- package/dist/browser.d.ts +1 -0
- package/dist/browser.js +2 -0
- package/dist/buy-tool.d.ts +45 -8
- package/dist/buy-tool.js +228 -27
- package/dist/client.d.ts +30 -0
- package/dist/client.js +11 -0
- package/dist/definition.js +2 -0
- package/dist/direct.d.ts +5 -2
- package/dist/index.d.ts +4 -2
- package/dist/index.js +3 -1
- package/dist/messages.js +4 -4
- package/dist/model-view.d.ts +48 -4
- package/dist/model-view.js +208 -27
- package/dist/order.d.ts +119 -0
- package/dist/order.js +155 -0
- package/dist/sentences.d.ts +3 -0
- package/dist/sentences.js +29 -0
- package/dist/shopper-message.d.ts +10 -0
- package/dist/shopper-message.js +51 -11
- package/dist/texting.d.ts +81 -0
- package/dist/texting.js +347 -0
- package/package.json +1 -1
package/dist/buy-tool.js
CHANGED
|
@@ -33,12 +33,13 @@
|
|
|
33
33
|
*/
|
|
34
34
|
import { AgentcardApiError, AgentcardClient, cartBuildingOf, } from './client.js';
|
|
35
35
|
import { buyToolDefinition, buyToolStandardSchema, openAIBuyTool, parseBuyToolInput, } from './definition.js';
|
|
36
|
-
import { directCall } from './direct.js';
|
|
37
|
-
import { forModel, forModelError, pickId, productPrice, productCardKey,
|
|
36
|
+
import { directCall, isProductAsk } from './direct.js';
|
|
37
|
+
import { forModel, forModelError, pickId, productPrice, productCardKey, shownProducts, shownStores, stripLinks } from './model-view.js';
|
|
38
|
+
import { choiceLines, leadsIntoList, messageForShopper, messagesListChoice, replyAsWritten, textingStyle } from './texting.js';
|
|
38
39
|
import { messageStream, paceOf, sleep, toMessages, typingHook } from './messages.js';
|
|
39
40
|
import { isStale } from './shopper-message.js';
|
|
40
41
|
import { actionLinkId, isActionLink } from './action-links.js';
|
|
41
|
-
import { ACK_REACTION, ackAt, answersQuestion, instantAck as instantAckOf, isNewRequest, isPickText, keepsYes, namesStore, nextTemplate, slowStepLine, waitAt } from './ack.js';
|
|
42
|
+
import { ACK_REACTION, ackAt, answersCheckout, answersQuestion, holdsBack, instantAck as instantAckOf, isNewRequest, isPickText, keepsYes, namesStore, nextTemplate, slowStepLine, waitAt } from './ack.js';
|
|
42
43
|
/** The action a product of `merchant` takes (BuyToolProductAction). */
|
|
43
44
|
export function productAction(merchant) {
|
|
44
45
|
if (merchant === 'reservations')
|
|
@@ -145,6 +146,70 @@ const NOTHING_TO_CONFIRM = 'There is no cart to confirm yet. Ask the shopper wha
|
|
|
145
146
|
const ADDRESS_CHANGED = 'The delivery address changed since this cart was built. Ask for the cart again, and Agentcard prices it for the new address.';
|
|
146
147
|
const STORE_PICK_GONE = "The store the shopper picked is no longer among the stores Agentcard found, so no store was chosen. Call this tool again with the shopper's words and store_id null, and Agentcard searches again.";
|
|
147
148
|
const PICK_GONE = "The product the shopper picked is no longer among the products Agentcard found, so nothing was added. Call this tool again with the shopper's words and product_id null, and Agentcard searches again.";
|
|
149
|
+
/** One cart line as the order holds it: the product, its variant, its choices and its quantity. Prices are left out. */
|
|
150
|
+
const lineKey = (line) => JSON.stringify([line.product_id ?? String(line.name ?? '').trim().toLowerCase(), line.variant ?? null, [...(line.options ?? [])].sort(), line.qty]);
|
|
151
|
+
/**
|
|
152
|
+
* How two matched lines' prices stand: false when a price they both name moved, true when one they both name agrees, and
|
|
153
|
+
* null when they name none in common, so the lines say nothing about what they cost.
|
|
154
|
+
*/
|
|
155
|
+
function linePriceStands(a, b) {
|
|
156
|
+
const named = (x, y) => typeof x === 'number' && typeof y === 'number' ? x === y : null;
|
|
157
|
+
const read = [named(a.priceCents, b.priceCents), named(a.lineTotalCents, b.lineTotalCents)];
|
|
158
|
+
if (read.includes(false))
|
|
159
|
+
return false;
|
|
160
|
+
return read.includes(true) ? true : null;
|
|
161
|
+
}
|
|
162
|
+
/**
|
|
163
|
+
* True when two carts are the same order: the same store, the same items in the same quantities with the same choices,
|
|
164
|
+
* and the same price for those items. Only the total may differ, as when the store prices its fees, tax and delivery in
|
|
165
|
+
* once the shopper's account holds the cart (Mendocino Farms on DoorDash, 2026-10-27: about $15.90, then $17.89).
|
|
166
|
+
*
|
|
167
|
+
* What the items cost has to be shown to be the same: every matched line's prices agree, or both carts name a subtotal
|
|
168
|
+
* and it is equal. A line whose price moved, two lines whose prices swapped under one subtotal, and a cart nothing
|
|
169
|
+
* prices are each a different order, so the shopper is asked again (review of #11087).
|
|
170
|
+
*/
|
|
171
|
+
function sameOrder(a, b) {
|
|
172
|
+
if (a.merchant !== b.merchant || (a.store?.id ?? null) !== (b.store?.id ?? null))
|
|
173
|
+
return false;
|
|
174
|
+
if ((a.currency ?? null) !== (b.currency ?? null))
|
|
175
|
+
return false;
|
|
176
|
+
const subtotals = typeof a.subtotalCents === 'number' && typeof b.subtotalCents === 'number' ? a.subtotalCents === b.subtotalCents : null;
|
|
177
|
+
if (subtotals === false)
|
|
178
|
+
return false;
|
|
179
|
+
const lines = (cart) => [...(cart.items ?? [])].sort((p, q) => (lineKey(p) < lineKey(q) ? -1 : lineKey(p) > lineKey(q) ? 1 : 0));
|
|
180
|
+
const [x, y] = [lines(a), lines(b)];
|
|
181
|
+
if (x.length === 0 || x.length !== y.length || !x.every((line, i) => lineKey(line) === lineKey(y[i])))
|
|
182
|
+
return false;
|
|
183
|
+
const prices = x.map((line, i) => linePriceStands(line, y[i]));
|
|
184
|
+
if (prices.includes(false))
|
|
185
|
+
return false;
|
|
186
|
+
return subtotals === true || prices.every((stands) => stands === true);
|
|
187
|
+
}
|
|
188
|
+
/** True when `now` holds an item `before` did not: the call put something new in the cart. */
|
|
189
|
+
function addsItem(now, before) {
|
|
190
|
+
if (!now || (now.items ?? []).length === 0)
|
|
191
|
+
return false;
|
|
192
|
+
const had = new Set((before && before.merchant === now.merchant ? before.items ?? [] : []).map((line) => lineKey({ ...line, qty: 0 })));
|
|
193
|
+
return (now.items ?? []).some((line) => !had.has(lineKey({ ...line, qty: 0 })));
|
|
194
|
+
}
|
|
195
|
+
/**
|
|
196
|
+
* The yes left standing when a confirm stopped for a step outside the chat, or null. The cart the confirm stopped on must
|
|
197
|
+
* be the same order as the one the yes covers; the total the shopper said yes to stays the one they saw.
|
|
198
|
+
*/
|
|
199
|
+
function yesAfter(result, yes) {
|
|
200
|
+
const stopped = result.cart ?? (result.carts.length === 1 ? result.carts[0] : null);
|
|
201
|
+
if ('carried' in yes) {
|
|
202
|
+
if (stopped && !sameOrder(stopped, yes.carried.cart))
|
|
203
|
+
return null;
|
|
204
|
+
return { ...yes.carried, cart: stopped ?? yes.carried.cart, conversationId: result.conversation_id ?? yes.carried.conversationId };
|
|
205
|
+
}
|
|
206
|
+
const seen = yes.shown ?? stopped;
|
|
207
|
+
if (!seen || (yes.shown && stopped && !sameOrder(yes.shown, stopped)))
|
|
208
|
+
return null;
|
|
209
|
+
return { conversationId: result.conversation_id, cart: stopped ?? seen, totalCents: seen.totalCents };
|
|
210
|
+
}
|
|
211
|
+
/** The words of the call a `nextStep` turn's `direct` makes. They never reach Agentcard: the turn's calls send no ask. */
|
|
212
|
+
const NEXT_STEP_ASK = 'take the next step';
|
|
148
213
|
/** What a call of a `nextStep` turn answers with when the shopper has not said yes to the cart on the table. */
|
|
149
214
|
const ASK_FOR_YES = "The cart is ready, and the shopper has not said yes to it in the chat, so nothing was confirmed. Say the cart's total in one short line and ask for their yes.";
|
|
150
215
|
const STEP_AFTER_DECLINE = 'The shopper declined the approval, so nothing was charged and no order was placed. The cart is still here. Say so in one short line and offer the way forward: their yes sends a new approval link, or they can change the order.';
|
|
@@ -264,6 +329,7 @@ function answerOf(turn) {
|
|
|
264
329
|
store_pick: turn.store_pick,
|
|
265
330
|
hashes: hashesOf(turn.cart, turn.carts),
|
|
266
331
|
cart_building: turn.cart_building,
|
|
332
|
+
choice: turn.choice ?? null,
|
|
267
333
|
envelope: turn.raw,
|
|
268
334
|
};
|
|
269
335
|
}
|
|
@@ -689,6 +755,9 @@ export function buyTool(options) {
|
|
|
689
755
|
if (o.replyAsWritten !== undefined && typeof o.replyAsWritten !== 'boolean')
|
|
690
756
|
throw new TypeError('Pass replyAsWritten as true or false.');
|
|
691
757
|
const writtenByDefault = o.replyAsWritten === true;
|
|
758
|
+
if (o.textingStyle !== undefined && typeof o.textingStyle !== 'boolean')
|
|
759
|
+
throw new TypeError('Pass textingStyle as true or false.');
|
|
760
|
+
const textingByDefault = o.textingStyle === true;
|
|
692
761
|
if (o.direct !== undefined && typeof o.direct !== 'boolean')
|
|
693
762
|
throw new TypeError('Pass direct as true or false.');
|
|
694
763
|
const directOn = o.direct === true;
|
|
@@ -749,6 +818,7 @@ export function buyTool(options) {
|
|
|
749
818
|
onAvailability: o.onAvailability,
|
|
750
819
|
onProducts: o.onProducts,
|
|
751
820
|
onText: o.onText,
|
|
821
|
+
texting: textingByDefault,
|
|
752
822
|
onCart: o.onCart,
|
|
753
823
|
links: null,
|
|
754
824
|
results: null,
|
|
@@ -781,14 +851,25 @@ export function buyTool(options) {
|
|
|
781
851
|
/** The conversation and the carts of the last answer, for a `nextStep` call that cannot read the conversation. */
|
|
782
852
|
let lastConversationId = null;
|
|
783
853
|
let lastCarts = [];
|
|
784
|
-
/** The
|
|
785
|
-
let
|
|
854
|
+
/** The cart the last answer showed: the cart a confirm sent now confirms. */
|
|
855
|
+
let lastShownCart = null;
|
|
786
856
|
/**
|
|
787
|
-
* The yes the shopper gave in the chat to one cart,
|
|
788
|
-
*
|
|
789
|
-
*
|
|
857
|
+
* The yes the shopper gave in the chat to one cart, when the confirm it sent stopped for a step outside the chat: a card
|
|
858
|
+
* to add or a store to sign in to. A `nextStep` turn confirms the cart once the step is done, as long as it holds the
|
|
859
|
+
* same items at the same store (`sameOrder`): a total that moved because the store priced its fees and tax in keeps the
|
|
860
|
+
* yes, and the turn says the new total. Any shopper message that is not a plain go-ahead, `ok` or `done` takes it back,
|
|
861
|
+
* and so does a declined approval.
|
|
790
862
|
*/
|
|
791
863
|
let carriedYes = null;
|
|
864
|
+
/**
|
|
865
|
+
* The go-ahead of the shopper's pick, when they picked what is in the cart and answered only the checkout's questions
|
|
866
|
+
* since (address, phone, name, email, a yes to an address read back). A `nextStep` turn after a saved card or a store
|
|
867
|
+
* sign-in confirms that cart as it stands, when it is the same order (`sameOrder`), whatever the Vault does: the approval
|
|
868
|
+
* link is the shopper's approval. Any other shopper message takes it back, and so do a cart that changed and a declined approval.
|
|
869
|
+
*/
|
|
870
|
+
let pickedOrder = null;
|
|
871
|
+
/** How many shopper messages took a pick's go-ahead back: a call that started before one leaves no pick behind. */
|
|
872
|
+
let pickWithdrawals = 0;
|
|
792
873
|
/** The add-card link an answer carried while it asked for the store sign-in first: the card is the step after it. */
|
|
793
874
|
let cardAfterSignIn = null;
|
|
794
875
|
/** The shopper's words that never ran because they were not connected yet: they run once their card is saved. */
|
|
@@ -834,7 +915,17 @@ export function buyTool(options) {
|
|
|
834
915
|
* way a shopper message reaches the tool runs it: `turn`, `instantAck` and `instantReaction` (review of #9220).
|
|
835
916
|
*/
|
|
836
917
|
const withdrawUnlessGoAhead = (shopperText) => {
|
|
837
|
-
|
|
918
|
+
const text = String(shopperText ?? '');
|
|
919
|
+
const goAhead = keepsYes(text);
|
|
920
|
+
// A pick's go-ahead also stands through the answers the checkout asks for, and through the answer that lets the store
|
|
921
|
+
// cart the pick, such as a size, while no cart holds it yet.
|
|
922
|
+
// Counted with no pick standing too, so a pick still running when the shopper says "no wait" leaves none behind.
|
|
923
|
+
// A short `cancel`, `wait` or `no` answers the question's shape, so it never counts as the answer the pick waits on.
|
|
924
|
+
if (!goAhead && !answersCheckout(text, { nextAction: lastNextAction, asked: lastReply }) && !(pickedOrder?.cart === null && answersQuestion(text, lastReply) && !holdsBack(text))) {
|
|
925
|
+
pickedOrder = null;
|
|
926
|
+
pickWithdrawals++;
|
|
927
|
+
}
|
|
928
|
+
if (goAhead)
|
|
838
929
|
return;
|
|
839
930
|
carriedYes = null;
|
|
840
931
|
unconnectedAsk = null;
|
|
@@ -1322,7 +1413,7 @@ export function buyTool(options) {
|
|
|
1322
1413
|
return;
|
|
1323
1414
|
const outside = result.status === 'needs_card' || result.next_action === 'add_card' || result.next_action === 'store_login';
|
|
1324
1415
|
if (call.confirm)
|
|
1325
|
-
carriedYes = outside
|
|
1416
|
+
carriedYes = outside ? yesAfter(result, call.yes) : null;
|
|
1326
1417
|
if (result.next_action === 'store_login' && turn?.add_card_url)
|
|
1327
1418
|
cardAfterSignIn = { url: turn.add_card_url, expiresAt: turn.add_card_expires_at };
|
|
1328
1419
|
else if (result.next_action !== 'store_login')
|
|
@@ -1330,6 +1421,45 @@ export function buyTool(options) {
|
|
|
1330
1421
|
if (!call.confirm && call.ask !== '')
|
|
1331
1422
|
unconnectedAsk = result.error_code === 'user_not_connected' ? call.ask : null;
|
|
1332
1423
|
};
|
|
1424
|
+
/**
|
|
1425
|
+
* What a shopper's own call leaves of a pick's go-ahead (`pickedOrder`). A pick, a tap on a product's card or a request
|
|
1426
|
+
* to buy it, that leaves one cart and no list of products to choose from starts one, with that cart; one whose store still
|
|
1427
|
+
* asks a question before it carts the pick starts one with no cart yet. A later answer keeps it while the cart stays the
|
|
1428
|
+
* same order, and notes the cart as the chat shows it now; an answer whose cart changed, another purchase, more than one
|
|
1429
|
+
* cart, or a confirm of the shopper's own ends it. `heard` is false for a call whose shopper message no turn read
|
|
1430
|
+
* (`withdrawUnlessGoAhead`): its words must then answer the checkout too.
|
|
1431
|
+
*/
|
|
1432
|
+
const notePick = (result, call) => {
|
|
1433
|
+
if (call.pickWithdrawalsAtStart !== pickWithdrawals)
|
|
1434
|
+
return;
|
|
1435
|
+
const now = result.cart ?? (result.carts.length === 1 ? result.carts[0] : null);
|
|
1436
|
+
if (call.picking) {
|
|
1437
|
+
// A search that listed products beside a cart already there picked nothing in it; one that carted what it names did.
|
|
1438
|
+
const listed = shownProducts(result.products).length > 0;
|
|
1439
|
+
pickedOrder = result.carts.length > 1
|
|
1440
|
+
? null
|
|
1441
|
+
: now && (!listed || call.added)
|
|
1442
|
+
? { conversationId: result.conversation_id, cart: now }
|
|
1443
|
+
: !now && !listed && result.error_code === null
|
|
1444
|
+
? { conversationId: result.conversation_id, cart: null }
|
|
1445
|
+
: null;
|
|
1446
|
+
return;
|
|
1447
|
+
}
|
|
1448
|
+
const picked = pickedOrder;
|
|
1449
|
+
if (!picked)
|
|
1450
|
+
return;
|
|
1451
|
+
if (call.confirm || result.carts.length > 1 || (picked.conversationId && result.conversation_id && result.conversation_id !== picked.conversationId)) {
|
|
1452
|
+
pickedOrder = null;
|
|
1453
|
+
return;
|
|
1454
|
+
}
|
|
1455
|
+
if (!call.heard && !keepsYes(call.ask) && !answersCheckout(call.ask, { nextAction: call.asked.nextAction, asked: call.asked.reply })) {
|
|
1456
|
+
pickedOrder = null;
|
|
1457
|
+
return;
|
|
1458
|
+
}
|
|
1459
|
+
if (!now)
|
|
1460
|
+
return;
|
|
1461
|
+
pickedOrder = picked.cart === null || sameOrder(now, picked.cart) ? { conversationId: picked.conversationId ?? result.conversation_id, cart: now } : null;
|
|
1462
|
+
};
|
|
1333
1463
|
/** An answer the tool writes itself, from where the purchase stands, with nothing sent to Agentcard. */
|
|
1334
1464
|
const stepResult = (fields) => ({
|
|
1335
1465
|
messages: [],
|
|
@@ -1371,6 +1501,7 @@ export function buyTool(options) {
|
|
|
1371
1501
|
const standing = { cart: one ?? carts.at(-1) ?? null, carts, hashes: carts.map((c) => c.hash), conversation_id: conversationId };
|
|
1372
1502
|
if (event.kind === 'approval_declined') {
|
|
1373
1503
|
carriedYes = null;
|
|
1504
|
+
pickedOrder = null;
|
|
1374
1505
|
return { local: stepResult({ ...standing, status: 'needs_input', reply: carts.length > 0 ? STEP_AFTER_DECLINE : NO_CART_WAITING }) };
|
|
1375
1506
|
}
|
|
1376
1507
|
if (event.kind === 'card_added' && unconnectedAsk) {
|
|
@@ -1384,9 +1515,28 @@ export function buyTool(options) {
|
|
|
1384
1515
|
cardAfterSignIn = null;
|
|
1385
1516
|
return { local: stepResult({ ...standing, status: 'needs_card', reply: CARD_AFTER_SIGN_IN, links: [{ kind: 'vault', url: link.url, expiresAt: link.expiresAt }], next_action: 'add_card' }) };
|
|
1386
1517
|
}
|
|
1387
|
-
|
|
1518
|
+
// The shopper's yes goes on past the step they just finished: the same items at the same store, in the same purchase,
|
|
1519
|
+
// confirmed by the cart's hash as it stands now. A total that moved because the store priced its fees and tax in keeps
|
|
1520
|
+
// the yes, and the answer says the new total beside the next ask; the approval link is still the shopper's consent to
|
|
1521
|
+
// the payment (Karen, 2026-10-27).
|
|
1522
|
+
const yes = carriedYes;
|
|
1523
|
+
const inPurchase = (at) => at === null || at === conversationId;
|
|
1524
|
+
if (yes && one && conversationId && inPurchase(yes.conversationId) && sameOrder(one, yes.cart)) {
|
|
1388
1525
|
carriedYes = null;
|
|
1389
|
-
return { body: { confirm: one.hash, conversation_id: conversationId, payment_source: 'vault', ...who } };
|
|
1526
|
+
return { body: { confirm: one.hash, conversation_id: conversationId, payment_source: 'vault', ...who }, yes: { ...yes, cart: one, conversationId } };
|
|
1527
|
+
}
|
|
1528
|
+
// The shopper picked what is in the cart and finished the step outside the chat: finishing it is their go-ahead for the
|
|
1529
|
+
// same order, confirmed by the cart's hash as it stands now, and the approval link stays their consent to the payment
|
|
1530
|
+
// (Karen, 2026-10-27: "we are again asking for confirmation after user adds a card. we should assume they want to move
|
|
1531
|
+
// forward"). That holds whatever the shopper's Vault does: the approval link is the approval (Karen, 2026-10-27: "That
|
|
1532
|
+
// is the goal of the approval link. You shouldn't be asking again. Take the approval from the approval link as the
|
|
1533
|
+
// approval.").
|
|
1534
|
+
const picked = pickedOrder;
|
|
1535
|
+
if (picked?.cart && one && conversationId && inPurchase(picked.conversationId) && (event.kind === 'card_added' || event.kind === 'store_connected')) {
|
|
1536
|
+
pickedOrder = null;
|
|
1537
|
+
if (sameOrder(one, picked.cart)) {
|
|
1538
|
+
return { body: { confirm: one.hash, conversation_id: conversationId, payment_source: 'vault', ...who }, yes: { conversationId, cart: one, totalCents: picked.cart.totalCents, picked: true } };
|
|
1539
|
+
}
|
|
1390
1540
|
}
|
|
1391
1541
|
// A checkout the shopper already moved past their yes (an approval link waiting, or the order going through) is reported as
|
|
1392
1542
|
// it stands: the cart stays while it waits, and asking for another yes would contradict the link they have (review of #9220).
|
|
@@ -1443,13 +1593,22 @@ export function buyTool(options) {
|
|
|
1443
1593
|
...(client.company ? { external_user_id: externalUserId } : {}),
|
|
1444
1594
|
};
|
|
1445
1595
|
// The cart a confirm sent now confirms, for the yes it carries past a step outside the chat.
|
|
1446
|
-
const
|
|
1596
|
+
const yesShown = confirm ? lastShownCart : null;
|
|
1597
|
+
// A pick of what the shopper saw, by a product's id or its name, or a request to buy something: when its answer carts
|
|
1598
|
+
// it, the shopper's go-ahead for that cart (`notePick`). Read before the answer replaces what the shopper saw.
|
|
1599
|
+
const picksShown = !confirm && (!!pick?.product_id || pickOf(ask) !== null || isProductAsk(ask));
|
|
1600
|
+
const cartBefore = lastShownCart;
|
|
1601
|
+
const pickWithdrawalsAtStart = pickWithdrawals;
|
|
1602
|
+
const askedBefore = { nextAction: lastNextAction, reply: lastReply };
|
|
1447
1603
|
// The first call after `instantAck` opens the turn of the message it answered, whose reply is the
|
|
1448
1604
|
// app's. Any other call that starts while another runs, or right after one ended, is the model
|
|
1449
1605
|
// working through the same turn. A call made through `buy.turn` belongs to that turn alone.
|
|
1606
|
+
// Whether a turn read the shopper's message (`withdrawUnlessGoAhead`): `buy.turn`, or the app's instant reply.
|
|
1607
|
+
let heard = !!explicit;
|
|
1450
1608
|
if (!explicit) {
|
|
1451
1609
|
const opened = appAck && Date.now() - appAck.at < APP_TURN_MS ? appAck : null;
|
|
1452
1610
|
appAck = null;
|
|
1611
|
+
heard = !!opened;
|
|
1453
1612
|
if (opened) {
|
|
1454
1613
|
// Only a reply the app sent quiets onWorking. A message such as `yes` that starts a slow search
|
|
1455
1614
|
// still gets a reply that names nothing, and a slow pick gets one that says it is on it.
|
|
@@ -1490,6 +1649,14 @@ export function buyTool(options) {
|
|
|
1490
1649
|
* and every message after it, so the shopper reads them in the order Agentcard wrote them.
|
|
1491
1650
|
*/
|
|
1492
1651
|
const waiting = [];
|
|
1652
|
+
/** True when the last message the shopper read ended in a colon or a question mark, so choices that open the next one are its list. */
|
|
1653
|
+
let leadIn = false;
|
|
1654
|
+
/** Shows one of Agentcard's messages through onText, its lists as bullets (messageForShopper); `told` keeps it as Agentcard wrote it. */
|
|
1655
|
+
const showText = async (text, conversationId) => {
|
|
1656
|
+
await t.onText(messageForShopper(text, leadIn, t.texting), { conversationId });
|
|
1657
|
+
leadIn = leadsIntoList(text);
|
|
1658
|
+
told.push(text);
|
|
1659
|
+
};
|
|
1493
1660
|
/** The products past the cards of the catalog this call heard last, for the count that follows it. */
|
|
1494
1661
|
let moreFound = null;
|
|
1495
1662
|
/** Whether this call's cards went out, and a count that came before them. */
|
|
@@ -1631,8 +1798,7 @@ export function buyTool(options) {
|
|
|
1631
1798
|
return;
|
|
1632
1799
|
}
|
|
1633
1800
|
try {
|
|
1634
|
-
await
|
|
1635
|
-
told.push(text);
|
|
1801
|
+
await showText(text, conversationId);
|
|
1636
1802
|
}
|
|
1637
1803
|
catch {
|
|
1638
1804
|
// A message the app failed to show was never read, so the model words the answer.
|
|
@@ -1695,8 +1861,7 @@ export function buyTool(options) {
|
|
|
1695
1861
|
continue;
|
|
1696
1862
|
}
|
|
1697
1863
|
try {
|
|
1698
|
-
await
|
|
1699
|
-
told.push(message.text);
|
|
1864
|
+
await showText(message.text, message.conversationId);
|
|
1700
1865
|
}
|
|
1701
1866
|
catch {
|
|
1702
1867
|
// A message the app failed to show was never read, so the model words the answer.
|
|
@@ -1705,8 +1870,7 @@ export function buyTool(options) {
|
|
|
1705
1870
|
const stepAsk = turn?.next_action ? CHAT_STEP_ASKS[turn.next_action] : undefined;
|
|
1706
1871
|
if (replaced && stepAsk && settled.reply.endsWith(stepAsk)) {
|
|
1707
1872
|
try {
|
|
1708
|
-
await
|
|
1709
|
-
told.push(stepAsk);
|
|
1873
|
+
await showText(stepAsk, settled.conversation_id);
|
|
1710
1874
|
}
|
|
1711
1875
|
catch {
|
|
1712
1876
|
// Not shown, so the model asks for the step.
|
|
@@ -1715,7 +1879,20 @@ export function buyTool(options) {
|
|
|
1715
1879
|
}
|
|
1716
1880
|
// Every message of Agentcard's own answer reached the shopper through onText, so the model adds nothing to it. An answer
|
|
1717
1881
|
// whose card ask gave way to the named step was read the same way once every message of it, that ask last, went out.
|
|
1718
|
-
|
|
1882
|
+
// A reply the shopper read that asks them to choose, with the values named inside a sentence or not at all, is followed
|
|
1883
|
+
// by one more message that lists every value on its own line (choiceLines), so no choice is read buried in prose.
|
|
1884
|
+
let choiceShown = true;
|
|
1885
|
+
if (t.onText && turn?.choice && told.length > 0 && !messagesListChoice(told, turn.choice)) {
|
|
1886
|
+
const lines = choiceLines(turn.choice);
|
|
1887
|
+
try {
|
|
1888
|
+
await t.onText(t.texting ? textingStyle(lines) : lines, { conversationId: turn.conversation_id || null });
|
|
1889
|
+
}
|
|
1890
|
+
catch {
|
|
1891
|
+
// Not shown, so the shopper has not read the whole answer: the model words it, with the choice it reads.
|
|
1892
|
+
choiceShown = false;
|
|
1893
|
+
}
|
|
1894
|
+
}
|
|
1895
|
+
const read = choiceShown && !!turn && (settled.reply === turn.reply ? readEvery(told, turn.messages) : replaced && readEvery(told, toMessages(settled.reply)));
|
|
1719
1896
|
const answer = withMessages(settled, turn);
|
|
1720
1897
|
const delivered = await deliver(answered.size > 0 ? { ...answer, products: withAnswers(answer.products) } : answer, { turn: callTurn, presented: turn?.catalog?.presented === true, owner: t }, send);
|
|
1721
1898
|
// A count that came before any card (find mode puts its cards in the answer) goes out once the answer's cards did.
|
|
@@ -1733,10 +1910,19 @@ export function buyTool(options) {
|
|
|
1733
1910
|
// The call waits for it, and one that fails to send fails the call, as a step link the turn does not hold does.
|
|
1734
1911
|
if (read)
|
|
1735
1912
|
await t.releaseNow?.();
|
|
1913
|
+
// A confirm the turn sent on the shopper's earlier yes names the total they said yes to beside the cart's total now. A
|
|
1914
|
+
// confirm Agentcard refused, or one whose answer holds another order or asks for a yes, carried nothing: the model asks.
|
|
1915
|
+
const carried = plan && 'body' in plan && plan.yes && turn && delivered.next_action !== 'confirm' ? plan.yes : null;
|
|
1916
|
+
const nowCart = !carried ? null : !delivered.cart ? carried.cart : sameOrder(delivered.cart, carried.cart) ? delivered.cart : null;
|
|
1917
|
+
const yesCarried = carried && nowCart
|
|
1918
|
+
? { said_yes_to_cents: carried.totalCents, total_cents: nowCart.totalCents, currency: nowCart.currency ?? null, ...(carried.picked ? { picked: true } : {}) }
|
|
1919
|
+
: null;
|
|
1736
1920
|
const result = {
|
|
1737
1921
|
...delivered,
|
|
1738
1922
|
...(t.ackDelivered ? { shopper_already_told_looking: true } : {}),
|
|
1739
1923
|
...(read ? { shopper_already_read_reply: true } : {}),
|
|
1924
|
+
...(confirmSent ? { confirm_sent: true } : {}),
|
|
1925
|
+
...(yesCarried ? { yes_carried: yesCarried } : {}),
|
|
1740
1926
|
};
|
|
1741
1927
|
lastNextAction = result.next_action;
|
|
1742
1928
|
lastReply = result.reply;
|
|
@@ -1749,12 +1935,19 @@ export function buyTool(options) {
|
|
|
1749
1935
|
if (result.conversation_id)
|
|
1750
1936
|
lastConversationId = result.conversation_id;
|
|
1751
1937
|
if (result.cart?.hash)
|
|
1752
|
-
|
|
1938
|
+
lastShownCart = result.cart;
|
|
1753
1939
|
// A shopper's own call, or the shopper's yes or words a turn taken on its own sent, leaves what the next step needs.
|
|
1754
1940
|
if (!t.event)
|
|
1755
|
-
noteShopperStep(result, turn, { confirm: !!confirm,
|
|
1941
|
+
noteShopperStep(result, turn, { confirm: !!confirm, yes: { shown: yesShown }, ask, withdrawalsAtStart });
|
|
1756
1942
|
else if (plan && 'body' in plan)
|
|
1757
|
-
noteShopperStep(result, turn, { confirm: confirmSent,
|
|
1943
|
+
noteShopperStep(result, turn, { confirm: confirmSent && !!plan.yes, yes: plan.yes ? { carried: plan.yes } : { shown: null }, ask: sent.ask ?? '', withdrawalsAtStart });
|
|
1944
|
+
if (!t.event && turn) {
|
|
1945
|
+
// Words that name something to buy and put a new item in the cart picked it too, as `the chicken pesto caprese from
|
|
1946
|
+
// mendocino farms` carts the dish it names.
|
|
1947
|
+
const added = result.pick?.in_cart === true || addsItem(result.cart, cartBefore);
|
|
1948
|
+
const picking = !confirm && (picksShown || result.pick?.in_cart === true || (isNewRequest(ask) && added));
|
|
1949
|
+
notePick(result, { picking, added, confirm: !!confirm, ask, heard, asked: askedBefore, pickWithdrawalsAtStart });
|
|
1950
|
+
}
|
|
1758
1951
|
// An answer that lists products replaces the names, even when every one is too short to keep.
|
|
1759
1952
|
const shown = shownProducts(result.products);
|
|
1760
1953
|
if (shown.length > 0)
|
|
@@ -1867,6 +2060,9 @@ export function buyTool(options) {
|
|
|
1867
2060
|
if (own.replyAsWritten !== undefined && typeof own.replyAsWritten !== 'boolean')
|
|
1868
2061
|
throw new TypeError('Pass replyAsWritten to the turn as true or false, or leave it out.');
|
|
1869
2062
|
const written = own.replyAsWritten ?? writtenByDefault;
|
|
2063
|
+
if (own.textingStyle !== undefined && typeof own.textingStyle !== 'boolean')
|
|
2064
|
+
throw new TypeError('Pass textingStyle to the turn as true or false, or leave it out.');
|
|
2065
|
+
const texting = own.textingStyle ?? textingByDefault;
|
|
1870
2066
|
const hearText = own.onText ?? o.onText;
|
|
1871
2067
|
// Messages are spaced only for an app that hears each one as it goes out.
|
|
1872
2068
|
const pauseOf = paceOf(own.pace);
|
|
@@ -1936,6 +2132,7 @@ export function buyTool(options) {
|
|
|
1936
2132
|
// Agentcard's message takes its place in the turn's sequence, after the items before it, and goes to onText alone, so
|
|
1937
2133
|
// an app that passes onItem too hears it once.
|
|
1938
2134
|
onText: hearText ? (text, info) => feed.push({ kind: 'text', text }, () => hearText(text, info)) : undefined,
|
|
2135
|
+
texting,
|
|
1939
2136
|
onCart: own.onCart ?? o.onCart,
|
|
1940
2137
|
tell: onItem || baseOnWorking ? (text) => feed.push({ kind: 'text', text }, () => (onItem ? onItem({ kind: 'text', text }) : baseOnWorking?.(text))) : undefined,
|
|
1941
2138
|
links: [],
|
|
@@ -2004,9 +2201,13 @@ export function buyTool(options) {
|
|
|
2004
2201
|
const results = state.results;
|
|
2005
2202
|
// A pick or an answer goes straight only after a turn that ended on Agentcard's answer, and never while a call still runs.
|
|
2006
2203
|
const answered = previousTurn !== null && (previousTurn.results?.length ?? 0) > 0;
|
|
2007
|
-
|
|
2008
|
-
|
|
2009
|
-
|
|
2204
|
+
// A `nextStep` turn's call is the same whatever the model writes (`stepPlan` reads where the purchase stands), so it goes
|
|
2205
|
+
// straight too: with `replyAsWritten`, a step whose answer is a link, such as the approval after a saved card, needs no model.
|
|
2206
|
+
const direct = !directOn || running > 0
|
|
2207
|
+
? null
|
|
2208
|
+
: event
|
|
2209
|
+
? { input: { ask: NEXT_STEP_ASK, confirm: false, new_order: false, product_id: null, store_id: null }, reason: 'next_step' }
|
|
2210
|
+
: directCall(String(shopperText ?? ''), { answered, nextAction: lastNextAction, asked: lastReply, products: lastAnswerProducts, stores: lastAnswerStores });
|
|
2010
2211
|
if (!event)
|
|
2011
2212
|
previousTurn = state;
|
|
2012
2213
|
// What Agentcard told the shopper that no history the model ran with holds yet.
|
|
@@ -2067,7 +2268,7 @@ export function buyTool(options) {
|
|
|
2067
2268
|
links: { get: () => [...links], enumerable: false },
|
|
2068
2269
|
results: { get: () => [...results], enumerable: false },
|
|
2069
2270
|
direct: { value: direct, enumerable: false },
|
|
2070
|
-
replyAsWritten: { get: () => (written && results.length > 0 ? replyAsWritten(results[results.length - 1]) : null), enumerable: false },
|
|
2271
|
+
replyAsWritten: { get: () => (written && results.length > 0 ? replyAsWritten(results[results.length - 1], { textingStyle: texting }) : null), enumerable: false },
|
|
2071
2272
|
items: { get: () => feed.items.map((item) => (item.kind === 'link' ? { kind: 'link', link: { ...item.link } } : { ...item })), enumerable: false },
|
|
2072
2273
|
// Each message joins the feed the moment it is cut, so a link the tool hands over during a pause never overtakes text already written.
|
|
2073
2274
|
// A step link held for this message goes out right after its last paragraph, once the stream ends.
|
package/dist/client.d.ts
CHANGED
|
@@ -373,6 +373,32 @@ export interface BuyStorePick {
|
|
|
373
373
|
name: string | null;
|
|
374
374
|
selected: boolean;
|
|
375
375
|
}
|
|
376
|
+
/** One value the shopper picks from. `id` is the option id the cart takes, null when the shopper's words answer it. */
|
|
377
|
+
export interface BuyChoiceOption {
|
|
378
|
+
id: string | null;
|
|
379
|
+
label: string;
|
|
380
|
+
/** What choosing it adds to the price, in cents: 0 when free, null when the store does not say. */
|
|
381
|
+
price_delta_cents: number | null;
|
|
382
|
+
}
|
|
383
|
+
/** One group of values, such as a sandwich's bread or a shirt's size. `min` and `max` count the values picked; `max` null for no limit. */
|
|
384
|
+
export interface BuyChoiceGroup {
|
|
385
|
+
name: string;
|
|
386
|
+
required: boolean;
|
|
387
|
+
min: number;
|
|
388
|
+
max: number | null;
|
|
389
|
+
options: BuyChoiceOption[];
|
|
390
|
+
}
|
|
391
|
+
/**
|
|
392
|
+
* The choice an answer's reply asks the shopper to make, as data: the question, the item it is about, where the values come
|
|
393
|
+
* from (`menu_item`: a restaurant item's options, `store_checkout`: a store's own size or colour question, `catalog_variant`:
|
|
394
|
+
* a product's sizes or colours in stock), and every group with its values.
|
|
395
|
+
*/
|
|
396
|
+
export interface BuyChoice {
|
|
397
|
+
question: string | null;
|
|
398
|
+
item: string | null;
|
|
399
|
+
source: string;
|
|
400
|
+
groups: BuyChoiceGroup[];
|
|
401
|
+
}
|
|
376
402
|
export interface BuyPaymentSource {
|
|
377
403
|
source: string;
|
|
378
404
|
brand: string | null;
|
|
@@ -435,6 +461,8 @@ export interface BuyTurn {
|
|
|
435
461
|
store_pick: BuyStorePick | null;
|
|
436
462
|
/** The cart the store's checkout is still building after this answer; null when none is, and on an older API. */
|
|
437
463
|
cart_building: BuyCartBuilding | null;
|
|
464
|
+
/** The choice the reply asks the shopper to make, each group with every value; null when it asks none, and on an older API. */
|
|
465
|
+
choice: BuyChoice | null;
|
|
438
466
|
/** The answer as Agentcard sent it. */
|
|
439
467
|
raw: Record<string, unknown>;
|
|
440
468
|
}
|
|
@@ -506,6 +534,8 @@ export declare class AgentcardApiError extends Error {
|
|
|
506
534
|
}
|
|
507
535
|
/** A turn as Agentcard answers it. */
|
|
508
536
|
export declare function turnOf(body: Record<string, unknown>): BuyTurn;
|
|
537
|
+
/** An answer's `choice`, its groups with at least one value each, or null when it carries none. */
|
|
538
|
+
export declare function choiceOf(value: unknown): BuyChoice | null;
|
|
509
539
|
/** A cart being built, as an answer, a refusal or the conversation read names it. */
|
|
510
540
|
export declare function cartBuildingOf(value: unknown): BuyCartBuilding | null;
|
|
511
541
|
/** The client every shopper's buy tool shares. Build one per set of credentials and keep it. */
|
package/dist/client.js
CHANGED
|
@@ -299,9 +299,20 @@ export function turnOf(body) {
|
|
|
299
299
|
stores: storesOf(body.stores),
|
|
300
300
|
store_pick: isRecord(body.store_pick) && str(body.store_pick.store_id) ? { store_id: body.store_pick.store_id, name: str(body.store_pick.name), selected: body.store_pick.selected === true } : null,
|
|
301
301
|
cart_building: cartBuildingOf(body.cart_building),
|
|
302
|
+
choice: choiceOf(body.choice),
|
|
302
303
|
raw: body,
|
|
303
304
|
};
|
|
304
305
|
}
|
|
306
|
+
/** An answer's `choice`, its groups with at least one value each, or null when it carries none. */
|
|
307
|
+
export function choiceOf(value) {
|
|
308
|
+
if (!isRecord(value) || !Array.isArray(value.groups))
|
|
309
|
+
return null;
|
|
310
|
+
const groups = value.groups.filter(isRecord).flatMap((g) => {
|
|
311
|
+
const options = (Array.isArray(g.options) ? g.options : []).filter(isRecord).flatMap((o) => str(o.label) ? [{ id: str(o.id), label: o.label.trim(), price_delta_cents: num(o.price_delta_cents) }] : []);
|
|
312
|
+
return options.length ? [{ name: str(g.name) ?? '', required: g.required === true, min: num(g.min) ?? 0, max: num(g.max), options }] : [];
|
|
313
|
+
});
|
|
314
|
+
return groups.length ? { question: str(value.question), item: str(value.item), source: str(value.source) ?? '', groups } : null;
|
|
315
|
+
}
|
|
305
316
|
/** A cart being built, as an answer, a refusal or the conversation read names it. */
|
|
306
317
|
export function cartBuildingOf(value) {
|
|
307
318
|
return isRecord(value) && str(value.store) ? { store: value.store, started_at: str(value.started_at) ?? '' } : null;
|
package/dist/definition.js
CHANGED
|
@@ -42,6 +42,7 @@ export const BUY_TOOL_DESCRIPTION = [
|
|
|
42
42
|
"Talk to the shopper only about their order, in their terms: what is in the cart, what was found and what they can do next. Leave out how it was done, even when `reply` says it: never mention this tool, `reply`, a field, a system, \"my end\" or what you can or can't see, the searches run, the stores checked or not, a timeout, account setup, or a link that did not go out. When parts of the result disagree, say what you are sure of and ask, never which part said what.",
|
|
43
43
|
'Never tell the shopper what has not happened yet or where things stand inside, such as "nothing\'s been charged yet", "your order isn\'t placed yet" or "the address isn\'t locked in", even when `reply` says it: ask for what is needed instead, such as "should it ship to 1 ferry plz?".',
|
|
44
44
|
'When `next_action` is `store_login`, `add_card` or `approve`, the message that asks for it is one short line and nothing else, even when `reply` says more, such as "sign in to doordash to place your order", "add your card to place the order" or "approve the payment to place your order (up to $19.02)": no recap of the cart, the address or the price, nothing about tax, delivery, shipping, a hold or what was charged, no word on when the link runs out, and never a request to tell you once it is done. On `approve`, put the cart\'s `max_charge` in the parentheses when it is set, and no other amount. Only when the shopper\'s last message picked what went in the cart does a line about the cart come first, as its own message; after an address, a phone number, a yes or a saved card, the ask is the whole reply.',
|
|
45
|
+
'When `yes_carried` is set, the shopper already said yes to this cart, or picked it (`yes_carried.picked`), and Agentcard went ahead on it once they finished the step outside the chat, so never ask for their yes again: ask only for the step `next_action` names. When `yes_carried.total_changed` is true, that one short line opens with the new total, the one price it names beside `max_charge`, such as "it\'s $17.89 with fees and tax now. approve the payment to place your order (up to $19.02)".',
|
|
45
46
|
'When `next_action` is `confirm`, ask in one short message: the items, one total, and to say yes to place it, such as "1 carolina black socks pack from walmart, about $9.57. say yes to place it". Leave out the fees one by one, the hold, and the address unless the shopper just changed it.',
|
|
46
47
|
'Every line you write reaches the shopper, a line before you call this tool too: write to them as "you", never about "the shopper" or "the user", and never say what you are about to do with this tool, such as "I\'ll add them now".',
|
|
47
48
|
'Say as fact only what the result shows: never promise what a store will do later, such as confirm a size when the order goes through.',
|
|
@@ -52,6 +53,7 @@ export const BUY_TOOL_DESCRIPTION = [
|
|
|
52
53
|
"When the shopper asks what you can do or which stores you shop at, answer without calling this tool and with no line saying you are looking: one message that names at most four stores plus the kinds of things you can do, such as food delivery and restaurant reservations, then one that asks what they need. Every shopper can buy from Amazon, Walmart, Target and Best Buy, so name those, and no other store unless a result showed it.",
|
|
53
54
|
'Each paragraph you write reaches the shopper as its own message: give each one job and keep it under about 20 words. On a cart turn, write the confirmation and the one next step, such as "added a little cheeseburger from five guys, about $12.17 before delivery and tax" and then "next, sign in to doordash with the link so i can place it": one total, never a price breakdown your app\'s cart already shows (an app that shows no cart lists each line with its price) and never a fee by its name, rate or formula, such as an order fee, card processing or 2.9% + $0.30, even when `reply` or `fee_lines` names it, unless the shopper asks what the fees are, no promise to show the final total later, and nothing about passkeys, master passwords or Agentcard not seeing the card number unless the shopper asks.',
|
|
54
55
|
'Never listing products again is for offering choices: when you confirm what is in the cart, name each line by its item, such as "1 Dawn Ultra dish soap, 38 oz", never "the one you picked, shown in the card".',
|
|
56
|
+
'When you list options or choices for the shopper, such as sizes, breads, times, or stores no card shows, write one per line, each starting with "• ", in the same paragraph as the line that asks which one, so the question and its choices reach the shopper as one message. When the result has `choice`, list every value it holds that way, with its extra price, such as "• Gluten-free bread (+$2.46)", even when `reply` names them inside a sentence. The products and stores the cards show are still never listed again.',
|
|
55
57
|
'Say how long a link works only when the shopper asks, with `link_expires_in`, such as "it works for about 24 hours", never as a raw date and time or in UTC, such as `approval_expires_at` holds; when `link_expires_in` is null, say nothing about when it runs out.',
|
|
56
58
|
'Never call a product the shopper was sent "the card", in any language ("la tarjeta", "o cartão"): to the shopper a card is how they pay. Call it by its name, or "the one i sent".',
|
|
57
59
|
'When the cart has `total_leaves_out`, its `total` is before those: say so, such as "about $18.41 before tax and shipping", and never say it is with or includes tax, shipping or delivery.',
|
package/dist/direct.d.ts
CHANGED
|
@@ -1,7 +1,10 @@
|
|
|
1
1
|
import type { BuyNextAction } from './client.js';
|
|
2
2
|
import type { BuyToolInput } from './definition.js';
|
|
3
|
-
/**
|
|
4
|
-
|
|
3
|
+
/**
|
|
4
|
+
* Why a message goes straight to Agentcard. `next_step` is a turn `buy.nextStep` opened: its call reads where the
|
|
5
|
+
* purchase stands and goes on from there whatever the model would write, so it never needs the model to decide it.
|
|
6
|
+
*/
|
|
7
|
+
export type DirectReason = 'product_ask' | 'pick' | 'answer' | 'confirm' | 'next_step';
|
|
5
8
|
/** The buy call for a message that goes straight to Agentcard, and why it does. */
|
|
6
9
|
export interface DirectCall {
|
|
7
10
|
input: BuyToolInput;
|
package/dist/index.d.ts
CHANGED
|
@@ -1,14 +1,16 @@
|
|
|
1
1
|
export { verifyAgentcardWebhook, AgentcardSignatureError, type AgentcardSignatureReason, type AgentcardEvent, type VerifyOptions, } from './verify.js';
|
|
2
2
|
export { readPurchaseMessage, type PurchaseMessage, type PurchaseMessageKind, type PurchaseFacts, } from './message.js';
|
|
3
3
|
export { readShopperMessage, isStale, SHOPPER_EVENT_TYPES, NEXT_STEP_KINDS, type NextStepKind, type ShopperMessage, type ShopperMessageKind, type ShopperFacts, type ShopperEventType, type OrderStatus, } from './shopper-message.js';
|
|
4
|
+
export { ORDER_STEPS, updateOrder, arrivalText, type ShopperOrder, type OrderStep, type OrderPathStep, type OrderStepState, type ArrivalTextOptions, } from './order.js';
|
|
4
5
|
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 AgentcardTraceOptions, type OtelApi, type TraceContext, type FetchLike, type FetchLikeResponse, type BuyProgress, type BuyProgressStage, type BuyCatalogUpdate, type BuyAvailability, type BuyAvailabilityUpdate, type BuyFoundUpdate, type BuyTurnStatus, type BuySegmentUpdate, type BuyCartUpdate, type BuyDeltaUpdate, type BuyStreamHandlers, type Shopper, type BuyRequest, type BuyDeliveryAddress, type BuyTurn, type BuyStatus, type BuyCart, type BuyCartBuilding, 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
|
+
export { AgentcardClient, AgentcardApiError, BUY_TURN_TIMEOUT_MS, DEFAULT_BASE_URL, type AgentcardClientOptions, type AgentcardTraceOptions, type OtelApi, type TraceContext, type FetchLike, type FetchLikeResponse, type BuyProgress, type BuyProgressStage, type BuyCatalogUpdate, type BuyAvailability, type BuyAvailabilityUpdate, type BuyFoundUpdate, type BuyTurnStatus, type BuySegmentUpdate, type BuyCartUpdate, type BuyDeltaUpdate, type BuyStreamHandlers, type Shopper, type BuyRequest, type BuyDeliveryAddress, type BuyTurn, type BuyStatus, type BuyCart, type BuyCartBuilding, 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, type BuyChoice, type BuyChoiceGroup, type BuyChoiceOption, } from './client.js';
|
|
6
7
|
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
8
|
export { toMessages, messageStream, typingDelay, type MessagePace, type MessageStream, type MessageStreamOptions, } from './messages.js';
|
|
8
9
|
export { buyTool, nextStepPrompt, type BuyTool, type BuyToolOptions, type BuyToolResult, type BuyToolStatus, type BuyToolLink, type BuyToolFound, type BuyToolAvailability, type BuyToolProduct, type BuyToolProductAction, productAction, type BuyToolStore, type BuyToolTurn, type BuyToolTurnItem, type BuyToolTurnOptions, type BuyToolTurnMessage, type BuyAckMode, } from './buy-tool.js';
|
|
9
10
|
export { instantAck, instantReaction, ackFor, keepsYes, ACK_REACTION, type AckForOptions } from './ack.js';
|
|
10
11
|
export { directCall, isProductAsk, type DirectCall, type DirectContext, type DirectReason } from './direct.js';
|
|
11
|
-
export { forModel, forModelError,
|
|
12
|
+
export { forModel, forModelError, stepAsWritten, withoutCardList, stripLinks, type BuyToolModelView, type BuyToolModelCart, type BuyToolModelProduct, type BuyToolModelStore, type ShownCard, } from './model-view.js';
|
|
13
|
+
export { asBullets, choiceLines, replyAsWritten, textingStyle, withChoice, type ReplyAsWrittenOptions } from './texting.js';
|
|
12
14
|
export { toAnthropicTool, toOpenRouterTool, type AnthropicBuyTool, type OpenRouterToolkit, type ZodLike, } from './adapters.js';
|
|
13
15
|
export { listenToAgentcard, type AgentcardListener, type ListenOptions, type ListenCursorStore, type StreamFetch, } from './listen.js';
|
|
14
16
|
export { ACTION_LINK_ENDINGS, ACTION_LINK_KINDS, actionLinkId, actionLinkOutcome, isActionLink, type ActionLinkKind, type ActionLinkMessage, type ActionLinkOutcome, } from './action-links.js';
|
package/dist/index.js
CHANGED
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
export { verifyAgentcardWebhook, AgentcardSignatureError, } from './verify.js';
|
|
2
2
|
export { readPurchaseMessage, } from './message.js';
|
|
3
3
|
export { readShopperMessage, isStale, SHOPPER_EVENT_TYPES, NEXT_STEP_KINDS, } from './shopper-message.js';
|
|
4
|
+
export { ORDER_STEPS, updateOrder, arrivalText, } from './order.js';
|
|
4
5
|
export { agentcardWebhook, purchaseAgentWebhook, } from './handler.js';
|
|
5
6
|
export { AgentcardClient, AgentcardApiError, BUY_TURN_TIMEOUT_MS, DEFAULT_BASE_URL, } from './client.js';
|
|
6
7
|
export { BUY_TOOL_NAME, BUY_TOOL_DESCRIPTION, buyToolDefinition, parseBuyToolInput, } from './definition.js';
|
|
@@ -8,7 +9,8 @@ export { toMessages, messageStream, typingDelay, } from './messages.js';
|
|
|
8
9
|
export { buyTool, nextStepPrompt, productAction, } from './buy-tool.js';
|
|
9
10
|
export { instantAck, instantReaction, ackFor, keepsYes, ACK_REACTION } from './ack.js';
|
|
10
11
|
export { directCall, isProductAsk } from './direct.js';
|
|
11
|
-
export { forModel, forModelError,
|
|
12
|
+
export { forModel, forModelError, stepAsWritten, withoutCardList, stripLinks, } from './model-view.js';
|
|
13
|
+
export { asBullets, choiceLines, replyAsWritten, textingStyle, withChoice } from './texting.js';
|
|
12
14
|
export { toAnthropicTool, toOpenRouterTool, } from './adapters.js';
|
|
13
15
|
export { listenToAgentcard, } from './listen.js';
|
|
14
16
|
export { ACTION_LINK_ENDINGS, ACTION_LINK_KINDS, actionLinkId, actionLinkOutcome, isActionLink, } from './action-links.js';
|
package/dist/messages.js
CHANGED
|
@@ -6,14 +6,14 @@
|
|
|
6
6
|
*/
|
|
7
7
|
/** A line holding nothing but spaces or tabs. */
|
|
8
8
|
const BLANK = /^[ \t]*$/;
|
|
9
|
-
/** A list item: `-`,
|
|
10
|
-
const LIST_ITEM = /^ {0,3}(?:[
|
|
9
|
+
/** A list item: `-`, `*`, `+` or the bullet `•`, or a number followed by `.` or `)`, then a space or the line's end. */
|
|
10
|
+
const LIST_ITEM = /^ {0,3}(?:[-*+•]|\d{1,9}[.)])(?:[ \t]|$)/;
|
|
11
11
|
/** A list item whose marker a space already follows, the only kind a line still being written is sure to stay. */
|
|
12
|
-
const LIST_ITEM_SPACED = /^ {0,3}(?:[
|
|
12
|
+
const LIST_ITEM_SPACED = /^ {0,3}(?:[-*+•]|\d{1,9}[.)])[ \t]/;
|
|
13
13
|
/** A line indented under a list item: two spaces or a tab, then text. */
|
|
14
14
|
const INDENTED = /^(?: {2,}|\t)\S/;
|
|
15
15
|
/** The start of a line that can still grow into a list item or an indented line once more text arrives. */
|
|
16
|
-
const MAYBE_ITEM = /^ {0,3}(?:[
|
|
16
|
+
const MAYBE_ITEM = /^ {0,3}(?:[-*+•]|\d{1,9}[.)]?)?$|^[ \t]*$/;
|
|
17
17
|
/** A code fence: three or more backticks or tildes, indented by at most three spaces. */
|
|
18
18
|
const FENCE = /^ {0,3}(`{3,}|~{3,})/;
|
|
19
19
|
/** How the line after a blank line inside a list reads: it continues the list, it ends it, or it is too short to tell. */
|