@agent-cards/commerce-sdk 0.18.9 → 0.18.11
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/dist/app-instructions.generated.d.ts +1 -1
- package/dist/app-instructions.generated.js +1 -1
- package/dist/buy-tool.d.ts +27 -0
- package/dist/buy-tool.js +72 -8
- package/dist/client.d.ts +79 -0
- package/dist/client.js +68 -3
- package/dist/definition.js +2 -2
- package/dist/events.d.ts +1 -1
- package/dist/events.js +2 -0
- package/dist/index.d.ts +2 -1
- package/dist/index.js +1 -0
- package/dist/model-view.d.ts +3 -2
- package/dist/model-view.js +11 -3
- package/dist/trip.d.ts +50 -0
- package/dist/trip.js +31 -0
- package/package.json +1 -1
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
/** The version of the voice package the text was copied from. */
|
|
2
|
-
export declare const VOICE_VERSION = "0.2.
|
|
2
|
+
export declare const VOICE_VERSION = "0.2.83";
|
|
3
3
|
export declare const APP_TEXT: {
|
|
4
4
|
readonly core: "You help a shopper buy what they ask for, with the `buy` tool from Agentcard.\n\nWhen the shopper asks what you can do, answer from these lines without calling the tool:\n- You buy from Amazon, Walmart, Target, Best Buy and thousands more stores. Say \"and thousands more stores\" every time you name the stores you shop, in a reply about one kind of thing too, such as \"travel gear from Amazon, Target and thousands more stores\". Never let a few names read as the whole list, never say \"most online stores\", \"plenty more\" or \"many more\", and never give a number of stores.\n- You plan a whole trip: the flights, a hotel and a rental car together. When they ask what you can do for travel, lead with the whole trip, then name the parts they can book on their own.\n- You order food delivery and book restaurant tables.\n\nHow you use the buy tool:\n- Call it for anything the shopper wants to find, buy, book or order, a trip with no dates or airport yet included: the tool asks for what it still needs.\n- Never say whether you can buy at a store the shopper names, either way. Only the tool knows, so say you will look and call it.\n- Never write a link. Each link reaches the shopper on its own, so when `link_sent` is true, tell them a link is waiting for them.\n- Tell the shopper no reply is needed only when `shopper_need_not_reply` is true.\n- Ask for one thing per message, the step `next_action` names, and leave every later step for a later message.\n- Never tell the shopper you can't show Markdown, a table or other formatting, and never say you keep to plain text.";
|
|
5
5
|
readonly markdown: "The app shows your words as Markdown. When the shopper asks for a table, a list or bold, write it in Markdown. Use a list or a table where it reads better than a sentence, and keep each message short all the same.";
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
// GENERATED by scripts/app-instructions.mjs from apps/agent-voice/kb/app.md. Do not edit: edit kb/app.md.
|
|
2
2
|
/** The version of the voice package the text was copied from. */
|
|
3
|
-
export const VOICE_VERSION = "0.2.
|
|
3
|
+
export const VOICE_VERSION = "0.2.83";
|
|
4
4
|
export const APP_TEXT = {
|
|
5
5
|
"core": "You help a shopper buy what they ask for, with the `buy` tool from Agentcard.\n\nWhen the shopper asks what you can do, answer from these lines without calling the tool:\n- You buy from Amazon, Walmart, Target, Best Buy and thousands more stores. Say \"and thousands more stores\" every time you name the stores you shop, in a reply about one kind of thing too, such as \"travel gear from Amazon, Target and thousands more stores\". Never let a few names read as the whole list, never say \"most online stores\", \"plenty more\" or \"many more\", and never give a number of stores.\n- You plan a whole trip: the flights, a hotel and a rental car together. When they ask what you can do for travel, lead with the whole trip, then name the parts they can book on their own.\n- You order food delivery and book restaurant tables.\n\nHow you use the buy tool:\n- Call it for anything the shopper wants to find, buy, book or order, a trip with no dates or airport yet included: the tool asks for what it still needs.\n- Never say whether you can buy at a store the shopper names, either way. Only the tool knows, so say you will look and call it.\n- Never write a link. Each link reaches the shopper on its own, so when `link_sent` is true, tell them a link is waiting for them.\n- Tell the shopper no reply is needed only when `shopper_need_not_reply` is true.\n- Ask for one thing per message, the step `next_action` names, and leave every later step for a later message.\n- Never tell the shopper you can't show Markdown, a table or other formatting, and never say you keep to plain text.",
|
|
6
6
|
"markdown": "The app shows your words as Markdown. When the shopper asks for a table, a list or bold, write it in Markdown. Use a list or a table where it reads better than a sentence, and keep each message short all the same.",
|
package/dist/buy-tool.d.ts
CHANGED
|
@@ -34,6 +34,7 @@
|
|
|
34
34
|
import type { BuyCardSize } from './card-size.js';
|
|
35
35
|
import type { BuyPlan } from './plan.js';
|
|
36
36
|
import type { BuyMerchantStatusUpdate, BuySearchStatus } from './search-status.js';
|
|
37
|
+
import type { BuyFreshness, BuyRecheck, BuyStoreCheckUpdate } from './client.js';
|
|
37
38
|
import type { BuyFulfilledBy } from './fulfilled-by.js';
|
|
38
39
|
import { AgentcardClient, type BuyCart, type BuyCartBuilding, type BuyChoice, type BuyCodeRequest, type BuyFlightItinerary, type BuyDeliveryAddress, type BuyNextAction, type BuyPick, type BuyProgress, type BuyTurnStatus, type BuyAvailability, type BuyStorePick, type BuyTurn, type FetchLike } from './client.js';
|
|
39
40
|
import { type BuyToolDefinition, type BuyToolInput, type BuyToolInputSchema, type BuyToolStandardSchema, type OpenAIBuyTool } from './definition.js';
|
|
@@ -137,6 +138,10 @@ export interface BuyToolLink {
|
|
|
137
138
|
* Absent on every other card, such as a retail product's.
|
|
138
139
|
*/
|
|
139
140
|
availability?: BuyAvailability;
|
|
141
|
+
/** On a product link: whether the product ships to the shopper's country, as on `BuyToolProduct.shipsToYou`. */
|
|
142
|
+
shipsToYou?: 'yes' | 'no';
|
|
143
|
+
/** On a product link: the note for a product that ranks down for where it ships, as on `BuyToolProduct.shipsNote`. */
|
|
144
|
+
shipsNote?: string;
|
|
140
145
|
}
|
|
141
146
|
/** A store the answer's reply presents to choose from. */
|
|
142
147
|
export interface BuyToolStore {
|
|
@@ -187,6 +192,10 @@ export interface BuyToolProduct {
|
|
|
187
192
|
linkSent: boolean;
|
|
188
193
|
/** For a place to book: whether it has room at the asked time. Absent on a product with no such question. */
|
|
189
194
|
availability?: BuyAvailability;
|
|
195
|
+
/** Whether the product ships to the shopper's country (`BuyCatalogItem.ships_to_you`): `yes` or `no`, and absent when Agentcard cannot say or an older API sends none. */
|
|
196
|
+
shipsToYou?: 'yes' | 'no';
|
|
197
|
+
/** A short note for a product that ranks down for where it ships ("ships from the UK", "price in £"). Absent when there is none. */
|
|
198
|
+
shipsNote?: string;
|
|
190
199
|
/** Who sells or books it, where that is not the store it names (`via StubHub`); absent when Agentcard named none. */
|
|
191
200
|
fulfilledBy?: BuyFulfilledBy;
|
|
192
201
|
/** The shopper's size as the store's read found it (`men's 10.5 in stock`); absent when none was asked or read. */
|
|
@@ -412,6 +421,18 @@ export interface BuyToolOptions extends BuyStoreWait {
|
|
|
412
421
|
* search starts, then what it found. One that throws costs nothing but the call to it.
|
|
413
422
|
*/
|
|
414
423
|
onMerchantStatus?: (update: BuyMerchantStatusUpdate) => void | Promise<void>;
|
|
424
|
+
/**
|
|
425
|
+
* Hears each store behind a card while Agentcard checks its price and stock, then the store's answer
|
|
426
|
+
* (`BuyStoreCheckUpdate`): `cardId` is the card's `productId`. One that throws costs nothing but the call to it.
|
|
427
|
+
*/
|
|
428
|
+
onStoreCheck?: (update: BuyStoreCheckUpdate) => void | Promise<void>;
|
|
429
|
+
/**
|
|
430
|
+
* Hears Agentcard read the shopper's quotes again (`BuyRecheck`): at the start, the line to show and each item checking;
|
|
431
|
+
* at the end, the answer and each item's result. One that throws costs nothing but the call to it.
|
|
432
|
+
*/
|
|
433
|
+
onRecheck?: (update: BuyRecheck) => void | Promise<void>;
|
|
434
|
+
/** Hears each quote's answer as its read lands (`BuyFreshness`). One that throws costs nothing but the call to it. */
|
|
435
|
+
onFreshness?: (update: BuyFreshness) => void | Promise<void>;
|
|
415
436
|
/**
|
|
416
437
|
* Hears the products a call's search found, the moment the search answers and before Agentcard writes its reply,
|
|
417
438
|
* with the conversation, when the search ran (`asOf`, as the answer's `catalog.as_of` names it) and the store's name.
|
|
@@ -641,6 +662,12 @@ export interface BuyToolTurnOptions extends BuyStoreWait {
|
|
|
641
662
|
onAvailability?: BuyToolOptions['onAvailability'];
|
|
642
663
|
/** Hears each store this turn's searches ask, as the tool's `onMerchantStatus` does. */
|
|
643
664
|
onMerchantStatus?: BuyToolOptions['onMerchantStatus'];
|
|
665
|
+
/** Hears each store behind this turn's cards as it is checked, as the tool's `onStoreCheck` does. */
|
|
666
|
+
onStoreCheck?: BuyToolOptions['onStoreCheck'];
|
|
667
|
+
/** Hears this turn's re-check of the shopper's quotes, as the tool's `onRecheck` does. */
|
|
668
|
+
onRecheck?: BuyToolOptions['onRecheck'];
|
|
669
|
+
/** Hears each quote's answer in this turn's re-check, as the tool's `onFreshness` does. */
|
|
670
|
+
onFreshness?: BuyToolOptions['onFreshness'];
|
|
644
671
|
/** Hears the products each search of this turn's calls found, the moment it answers, as the tool's `onProducts` does. */
|
|
645
672
|
onProducts?: BuyToolOptions['onProducts'];
|
|
646
673
|
/**
|
package/dist/buy-tool.js
CHANGED
|
@@ -223,12 +223,16 @@ export function nextStepPrompt(message) {
|
|
|
223
223
|
/**
|
|
224
224
|
* The pick as a later read of the conversation shows it: a pick the early answer found missing is in
|
|
225
225
|
* the cart once a line carries its catalog id, as its product id or, for a retail product whose id is
|
|
226
|
-
* its store page, as its page
|
|
226
|
+
* its store page, as its page, or names its card by `ref`. A catalog card carted at a store page has the
|
|
227
|
+
* page as its line's id: on 2026-11-01 (conv_611973099c62bc0cdb2c041b) a pan set carted at kohls.com read
|
|
228
|
+
* as missing, so the model told the shopper the card it had just added was gone. A pick already in the
|
|
229
|
+
* cart, or none, stays as it was.
|
|
227
230
|
*/
|
|
228
231
|
function pickAfter(pick, carts) {
|
|
229
232
|
if (!pick || pick.in_cart)
|
|
230
233
|
return pick;
|
|
231
|
-
const
|
|
234
|
+
const names = (ref) => !!ref && (ref === pick.product_id || ref === pick.ref);
|
|
235
|
+
const held = carts.some((c) => c.items.some((line) => line.product_id === pick.product_id || line.url === pick.product_id || names(line.ref)));
|
|
232
236
|
return held ? { ...pick, in_cart: true } : pick;
|
|
233
237
|
}
|
|
234
238
|
function hashesOf(cart, carts) {
|
|
@@ -242,7 +246,7 @@ export function productsOf(turn) {
|
|
|
242
246
|
/** A product's card as `sendLink` hears it: the id the model reads and a pick takes, never the store page, which is `url`. A flight option has no page at a store: its card carries no url, and its productId picks it. */
|
|
243
247
|
function productLink(product) {
|
|
244
248
|
const productId = pickId(product);
|
|
245
|
-
return { kind: 'product', url: product.url || null, title: product.name, price: productPrice(product), ...(productId ? { productId } : {}), action: product.action ?? 'buy', imageUrl: product.imageUrl, images: product.images ?? (product.imageUrl ? [product.imageUrl] : []), store: product.store ?? null, storeDomain: product.storeDomain ?? null, expiresAt: null, ...(product.availability ? { availability: product.availability } : {}), ...(product.fulfilledBy ? { fulfilledBy: product.fulfilledBy } : {}), ...(product.shopperSize ? { shopperSize: product.shopperSize } : {}) };
|
|
249
|
+
return { kind: 'product', url: product.url || null, title: product.name, price: productPrice(product), ...(productId ? { productId } : {}), action: product.action ?? 'buy', imageUrl: product.imageUrl, images: product.images ?? (product.imageUrl ? [product.imageUrl] : []), store: product.store ?? null, storeDomain: product.storeDomain ?? null, expiresAt: null, ...(product.availability ? { availability: product.availability } : {}), ...(product.fulfilledBy ? { fulfilledBy: product.fulfilledBy } : {}), ...(product.shipsToYou ? { shipsToYou: product.shipsToYou } : {}), ...(product.shipsNote ? { shipsNote: product.shipsNote } : {}), ...(product.shopperSize ? { shopperSize: product.shopperSize } : {}) };
|
|
246
250
|
}
|
|
247
251
|
/** A host name: dot-separated labels of letters, digits and hyphens, ending in a letters-only top-level label. */
|
|
248
252
|
const STORE_HOST = /^(?=.{1,253}$)(?:[a-z0-9](?:[a-z0-9-]{0,61}[a-z0-9])?\.)+[a-z]{2,63}$/;
|
|
@@ -278,6 +282,8 @@ function productsOfCatalog(catalog) {
|
|
|
278
282
|
action: productAction(catalog.merchant),
|
|
279
283
|
linkSent: false,
|
|
280
284
|
...(item.availability ? { availability: item.availability } : {}),
|
|
285
|
+
...(item.ships_to_you === 'yes' || item.ships_to_you === 'no' ? { shipsToYou: item.ships_to_you } : {}),
|
|
286
|
+
...(item.ships_note ? { shipsNote: item.ships_note } : {}),
|
|
281
287
|
...(item.fulfilled_by ? { fulfilledBy: item.fulfilled_by } : {}),
|
|
282
288
|
...(item.shopper_size ? { shopperSize: item.shopper_size } : {}),
|
|
283
289
|
}));
|
|
@@ -771,7 +777,7 @@ export function buyTool(options) {
|
|
|
771
777
|
if (o.stream !== undefined && o.stream !== false)
|
|
772
778
|
throw new TypeError('Pass stream as false to wait for each whole answer, or leave it out.');
|
|
773
779
|
const onProgress = o.onProgress;
|
|
774
|
-
for (const key of ['onStatus', 'onFound', 'onAvailability', 'onMerchantStatus']) {
|
|
780
|
+
for (const key of ['onStatus', 'onFound', 'onAvailability', 'onMerchantStatus', 'onStoreCheck', 'onRecheck', 'onFreshness']) {
|
|
775
781
|
if (o[key] !== undefined && typeof o[key] !== 'function')
|
|
776
782
|
throw new TypeError(`Pass ${key} as a function, or leave it out.`);
|
|
777
783
|
}
|
|
@@ -853,6 +859,9 @@ export function buyTool(options) {
|
|
|
853
859
|
onFound: o.onFound,
|
|
854
860
|
onAvailability: o.onAvailability,
|
|
855
861
|
onMerchantStatus: o.onMerchantStatus,
|
|
862
|
+
onStoreCheck: o.onStoreCheck,
|
|
863
|
+
onRecheck: o.onRecheck,
|
|
864
|
+
onFreshness: o.onFreshness,
|
|
856
865
|
onProducts: o.onProducts,
|
|
857
866
|
onText: o.onText,
|
|
858
867
|
texting: textingByDefault,
|
|
@@ -1921,9 +1930,16 @@ export function buyTool(options) {
|
|
|
1921
1930
|
},
|
|
1922
1931
|
}
|
|
1923
1932
|
: {}),
|
|
1924
|
-
...(early || t.onProducts
|
|
1933
|
+
...(early || t.onProducts || t.onStoreCheck
|
|
1925
1934
|
? {
|
|
1926
1935
|
onCatalog: async (update) => {
|
|
1936
|
+
// Each card's productId is noted as its catalog arrives, whether or not cards go out early, so a store
|
|
1937
|
+
// check that follows names the card the app drew.
|
|
1938
|
+
for (const product of [...productsOfCatalog(update.catalog), ...productsOfCatalog({ ...update.catalog, items: update.more })]) {
|
|
1939
|
+
const productId = pickId(product);
|
|
1940
|
+
if (productId)
|
|
1941
|
+
cardOfId.set(product.id, productId);
|
|
1942
|
+
}
|
|
1927
1943
|
try {
|
|
1928
1944
|
await t.onProducts?.(productsOfCatalog(update.catalog), { conversationId: update.conversationId, asOf: update.asOf, merchantName: update.catalog.merchant_name });
|
|
1929
1945
|
}
|
|
@@ -1934,8 +1950,9 @@ export function buyTool(options) {
|
|
|
1934
1950
|
const keepMore = () => { if (t.onFound && early)
|
|
1935
1951
|
moreFound = { conversationId: update.conversationId, links: productsOfCatalog({ ...update.catalog, items: update.more }).map(productLink) }; };
|
|
1936
1952
|
if (!early) {
|
|
1937
|
-
// Without early cards nothing goes out here, but a store that answered after the reply still joins the
|
|
1938
|
-
|
|
1953
|
+
// Without early cards nothing goes out here, but a store that answered after the reply still joins the
|
|
1954
|
+
// result for an app that draws products; one that asked only for store checks reads the answer as before.
|
|
1955
|
+
if (replyWritten && t.onProducts)
|
|
1939
1956
|
lateCards.push(...productsOfCatalog(update.catalog));
|
|
1940
1957
|
return;
|
|
1941
1958
|
}
|
|
@@ -1978,6 +1995,50 @@ export function buyTool(options) {
|
|
|
1978
1995
|
},
|
|
1979
1996
|
}
|
|
1980
1997
|
: {}),
|
|
1998
|
+
...(t.onStoreCheck
|
|
1999
|
+
? {
|
|
2000
|
+
onStoreCheck: async (update) => {
|
|
2001
|
+
try {
|
|
2002
|
+
await t.onStoreCheck({ ...update, cardId: cardOfId.get(update.cardId) ?? update.cardId });
|
|
2003
|
+
}
|
|
2004
|
+
catch {
|
|
2005
|
+
// The app's display failing never fails the turn.
|
|
2006
|
+
}
|
|
2007
|
+
},
|
|
2008
|
+
}
|
|
2009
|
+
: {}),
|
|
2010
|
+
...(t.onRecheck
|
|
2011
|
+
? {
|
|
2012
|
+
onRecheck: async (update) => {
|
|
2013
|
+
try {
|
|
2014
|
+
await t.onRecheck(update);
|
|
2015
|
+
}
|
|
2016
|
+
catch {
|
|
2017
|
+
// The app's display failing never fails the turn, and the wait line stays, so the shopper still hears one.
|
|
2018
|
+
return;
|
|
2019
|
+
}
|
|
2020
|
+
// The re-check's line, once the app showed it, is the turn's acknowledgement: no wait line follows it,
|
|
2021
|
+
// however long the reads run.
|
|
2022
|
+
if (update.phase === 'started' && update.text) {
|
|
2023
|
+
ack?.cancel();
|
|
2024
|
+
t.dropWaitLine?.();
|
|
2025
|
+
t.acked = true;
|
|
2026
|
+
}
|
|
2027
|
+
},
|
|
2028
|
+
}
|
|
2029
|
+
: {}),
|
|
2030
|
+
...(t.onFreshness
|
|
2031
|
+
? {
|
|
2032
|
+
onFreshness: async (update) => {
|
|
2033
|
+
try {
|
|
2034
|
+
await t.onFreshness(update);
|
|
2035
|
+
}
|
|
2036
|
+
catch {
|
|
2037
|
+
// The app's display failing never fails the turn.
|
|
2038
|
+
}
|
|
2039
|
+
},
|
|
2040
|
+
}
|
|
2041
|
+
: {}),
|
|
1981
2042
|
...(t.onText || writtenText
|
|
1982
2043
|
? {
|
|
1983
2044
|
onSegment: async ({ text, conversationId }) => {
|
|
@@ -2332,7 +2393,7 @@ export function buyTool(options) {
|
|
|
2332
2393
|
const own = options ?? {};
|
|
2333
2394
|
if (typeof own !== 'object')
|
|
2334
2395
|
throw new TypeError('Pass the turn options as an object, or leave them out.');
|
|
2335
|
-
for (const key of ['sendLink', 'onWorking', 'onReaction', 'onResult', 'onProgress', 'onStatus', 'onFound', 'onAvailability', 'onMerchantStatus', 'onItem', 'onMessage', 'onProducts', 'onText', 'onCart']) {
|
|
2396
|
+
for (const key of ['sendLink', 'onWorking', 'onReaction', 'onResult', 'onProgress', 'onStatus', 'onFound', 'onAvailability', 'onMerchantStatus', 'onStoreCheck', 'onRecheck', 'onFreshness', 'onItem', 'onMessage', 'onProducts', 'onText', 'onCart']) {
|
|
2336
2397
|
if (own[key] !== undefined && typeof own[key] !== 'function')
|
|
2337
2398
|
throw new TypeError(`Pass ${key} to the turn as a function, or leave it out.`);
|
|
2338
2399
|
}
|
|
@@ -2417,6 +2478,9 @@ export function buyTool(options) {
|
|
|
2417
2478
|
onFound: own.onFound ?? o.onFound,
|
|
2418
2479
|
onAvailability: own.onAvailability ?? o.onAvailability,
|
|
2419
2480
|
onMerchantStatus: own.onMerchantStatus ?? o.onMerchantStatus,
|
|
2481
|
+
onStoreCheck: own.onStoreCheck ?? o.onStoreCheck,
|
|
2482
|
+
onRecheck: own.onRecheck ?? o.onRecheck,
|
|
2483
|
+
onFreshness: own.onFreshness ?? o.onFreshness,
|
|
2420
2484
|
onProducts: own.onProducts ?? o.onProducts,
|
|
2421
2485
|
// Agentcard's message takes its place in the turn's sequence, after the items before it, and goes to onText alone, so
|
|
2422
2486
|
// an app that passes onItem too hears it once.
|
package/dist/client.d.ts
CHANGED
|
@@ -8,6 +8,7 @@
|
|
|
8
8
|
* @agent-cards/sdk (packages/checkout/src/buy.ts: turnOf, buyError), and the
|
|
9
9
|
* token cache follows its client (packages/checkout/src/client.ts accessToken).
|
|
10
10
|
*/
|
|
11
|
+
import { type BuyTrip } from './trip.js';
|
|
11
12
|
import { type BuyPlan } from './plan.js';
|
|
12
13
|
import { type BuyMerchantStatusUpdate, type BuySearchStatus } from './search-status.js';
|
|
13
14
|
import { type BuyFulfilledBy } from './fulfilled-by.js';
|
|
@@ -94,6 +95,26 @@ export interface BuyAvailabilityUpdate {
|
|
|
94
95
|
availability: BuyAvailability;
|
|
95
96
|
}>;
|
|
96
97
|
}
|
|
98
|
+
/**
|
|
99
|
+
* One store behind one card of a running turn, as Agentcard asks the store itself for its price and stock: `checking` until
|
|
100
|
+
* the store answers, then `confirmed` (with the store's own price in `priceCents` when it gave one), `sold_out` (the store
|
|
101
|
+
* leaves the card) or `unconfirmed` (the store gave no stock answer; the card keeps the catalog's word for it). Show
|
|
102
|
+
* "checking <storeName>…" on the card until its store answers.
|
|
103
|
+
*
|
|
104
|
+
* Agentcard checks at most eight stores a turn, in the order the cards show, and each store once, under the first card that
|
|
105
|
+
* lists it: a later card that lists the same store, and a store past the eighth, hear no update and keep the catalog's word.
|
|
106
|
+
*/
|
|
107
|
+
export interface BuyStoreCheckUpdate {
|
|
108
|
+
conversationId: string;
|
|
109
|
+
/** The card's catalog `id`. */
|
|
110
|
+
cardId: string;
|
|
111
|
+
/** The store's website, such as `walmart.com`. */
|
|
112
|
+
store: string;
|
|
113
|
+
/** The store as a shopper reads it, such as `Walmart`. */
|
|
114
|
+
storeName: string;
|
|
115
|
+
state: 'checking' | 'confirmed' | 'sold_out' | 'unconfirmed';
|
|
116
|
+
priceCents: number | null;
|
|
117
|
+
}
|
|
97
118
|
/** How many products a running turn found so far, and how many of them it shows: "8 of 23 shown". */
|
|
98
119
|
export interface BuyFoundUpdate {
|
|
99
120
|
conversationId: string;
|
|
@@ -175,6 +196,52 @@ export interface BuyStreamHandlers {
|
|
|
175
196
|
* follow one store, keep the updates `isMerchantStatusAt(update, 'walmart.com')` accepts. The answer's `search` holds every store.
|
|
176
197
|
*/
|
|
177
198
|
onMerchantStatus?: (update: BuyMerchantStatusUpdate) => void | Promise<void>;
|
|
199
|
+
/**
|
|
200
|
+
* Agentcard reads the shopper's quotes again: on their return after a gap, or before a yes to a price older than its
|
|
201
|
+
* window. Comes with `phase: 'started'`, the line to show at once and every item `checking`, then `phase: 'finished'` with
|
|
202
|
+
* the answer to show and every item's result. A changed price is a question: the turn's reply asks it and nothing is booked.
|
|
203
|
+
*/
|
|
204
|
+
onRecheck?: (update: BuyRecheck) => void | Promise<void>;
|
|
205
|
+
/** One item's answer as its read lands, so its status moves from checking to confirmed, changed or gone. */
|
|
206
|
+
onFreshness?: (update: BuyFreshness) => void | Promise<void>;
|
|
207
|
+
/** A store behind a card, while Agentcard checks its price and stock and once it answers: up to eight stores a turn, each under its first card. */
|
|
208
|
+
onStoreCheck?: (update: BuyStoreCheckUpdate) => void | Promise<void>;
|
|
209
|
+
}
|
|
210
|
+
/** Whether a quote read again still stands: `same` price, a `changed` one, or `gone` from the seller. */
|
|
211
|
+
export type BuyFreshnessState = 'same' | 'changed' | 'gone';
|
|
212
|
+
/** One quote after it was read again. Amounts are in the currency's smallest unit. */
|
|
213
|
+
export interface BuyFreshness {
|
|
214
|
+
conversationId: string | null;
|
|
215
|
+
ref: string;
|
|
216
|
+
kind: 'flight' | 'event_ticket' | 'hotel' | 'rental_car' | 'restaurant_table' | 'store_cart';
|
|
217
|
+
name: string;
|
|
218
|
+
/** Who sells it, where the name does not say: a store's name. Null for a trip's item. */
|
|
219
|
+
seller: string | null;
|
|
220
|
+
freshness: BuyFreshnessState;
|
|
221
|
+
/** The quiet status to show beside the item: `confirmed`, `changed` or `gone`. */
|
|
222
|
+
status: 'confirmed' | 'changed' | 'gone';
|
|
223
|
+
/** The status in words: `Confirmed`, `Now $239.00`, `Sold out`. */
|
|
224
|
+
text: string;
|
|
225
|
+
previousCents: number | null;
|
|
226
|
+
amountCents: number | null;
|
|
227
|
+
currency: string | null;
|
|
228
|
+
/** When the read saw this price, ISO 8601. */
|
|
229
|
+
checkedAt: string;
|
|
230
|
+
}
|
|
231
|
+
/** A re-check of the shopper's quotes, as it starts and as it ends. */
|
|
232
|
+
export interface BuyRecheck {
|
|
233
|
+
conversationId: string | null;
|
|
234
|
+
phase: 'started' | 'finished';
|
|
235
|
+
/** `return`: the shopper came back after a gap. `commit`: a yes to prices older than their window. */
|
|
236
|
+
reason: 'return' | 'commit';
|
|
237
|
+
/** The line to show: what is being read again when it starts, the answer when it ends. Null when no read answered. */
|
|
238
|
+
text: string | null;
|
|
239
|
+
/** At the start, each item with `status: 'checking'`; at the end, each item that answered. */
|
|
240
|
+
items: Array<Pick<BuyFreshness, 'ref' | 'kind' | 'name' | 'seller' | 'text'> & Omit<Partial<BuyFreshness>, 'status'> & {
|
|
241
|
+
status: 'checking' | BuyFreshness['status'];
|
|
242
|
+
}>;
|
|
243
|
+
/** Items whose read did not answer: they keep their old price, and the checkout's own amount check still holds. */
|
|
244
|
+
unread: string[];
|
|
178
245
|
}
|
|
179
246
|
export declare const DEFAULT_BASE_URL = "https://api.agentcard.sh";
|
|
180
247
|
/** The wait for one purchase turn: past Agentcard's own 110-second budget, so a slow turn answers `turn_timeout` rather than aborting here. */
|
|
@@ -440,6 +507,8 @@ export interface BuyFlightItinerary {
|
|
|
440
507
|
export interface BuyFlightParty {
|
|
441
508
|
adults: number;
|
|
442
509
|
children: number;
|
|
510
|
+
/** How many of `children` fly on an adult's lap, under 2. Absent when none, and from an older API. */
|
|
511
|
+
infants?: number;
|
|
443
512
|
totalCents: number;
|
|
444
513
|
perPassengerCents?: number[];
|
|
445
514
|
}
|
|
@@ -465,6 +534,14 @@ export interface BuyCatalogItem {
|
|
|
465
534
|
url: string | null;
|
|
466
535
|
/** Whether a place has room at the asked time (BuyAvailability). Absent on a product with no such question. */
|
|
467
536
|
availability?: BuyAvailability;
|
|
537
|
+
/**
|
|
538
|
+
* Whether the product ships to the shopper's country: `yes` or `no` when the store lists the countries it ships to and
|
|
539
|
+
* Agentcard knows where the shopper is, `unknown` otherwise and from an older API. Agentcard never leaves a product out for
|
|
540
|
+
* it, and ranks a `no` behind the products that match the ask as well; an app may filter on it.
|
|
541
|
+
*/
|
|
542
|
+
ships_to_you: 'yes' | 'no' | 'unknown';
|
|
543
|
+
/** A short note for a product that ranks down for where it ships ("ships from the UK", "price in £"), or null. */
|
|
544
|
+
ships_note: string | null;
|
|
468
545
|
/** Who sells or books the product, where that is not the store the card names (`booked with OpenTable`); absent when Agentcard named none. */
|
|
469
546
|
fulfilled_by?: BuyFulfilledBy;
|
|
470
547
|
/** The size the shopper named, as the store's read of this product found it (BuyCardSize); absent when none was asked or read. */
|
|
@@ -679,6 +756,8 @@ export interface BuyConversation {
|
|
|
679
756
|
last_checkout: BuyLastCheckout | null;
|
|
680
757
|
orders: BuyOrder[];
|
|
681
758
|
carts: BuyCart[];
|
|
759
|
+
/** A trip being planned: each background search's status and results, flights first; null for any other conversation (trip.ts). */
|
|
760
|
+
trip: BuyTrip | null;
|
|
682
761
|
/**
|
|
683
762
|
* The reply Agentcard wrote since the shopper's last message, once no turn runs: the answer a call that ended with
|
|
684
763
|
* `turn_timeout` never got. Null while a turn runs, when no reply was written since, and on an older API.
|
package/dist/client.js
CHANGED
|
@@ -8,6 +8,7 @@
|
|
|
8
8
|
* @agent-cards/sdk (packages/checkout/src/buy.ts: turnOf, buyError), and the
|
|
9
9
|
* token cache follows its client (packages/checkout/src/client.ts accessToken).
|
|
10
10
|
*/
|
|
11
|
+
import { tripOf } from './trip.js';
|
|
11
12
|
import { planOf } from './plan.js';
|
|
12
13
|
import { merchantStatusOf, searchOf } from './search-status.js';
|
|
13
14
|
import { isRecord } from './verify.js';
|
|
@@ -15,6 +16,7 @@ import { failedPartyOf, fulfilledByField } from './fulfilled-by.js';
|
|
|
15
16
|
import { cardSizeField } from './card-size.js';
|
|
16
17
|
import { listenToAgentcard } from './listen.js';
|
|
17
18
|
import { messageInChat } from './shopper-message.js';
|
|
19
|
+
const STORE_CHECK_STATES = new Set(['checking', 'confirmed', 'sold_out', 'unconfirmed']);
|
|
18
20
|
/**
|
|
19
21
|
* The words this SDK shows for a step whatever Agentcard's own line says, so every app reads the same line. `searching_wider`
|
|
20
22
|
* comes when a search across every store has shown no product for a while and keeps running; its products still come as cards.
|
|
@@ -23,7 +25,7 @@ export const STATUS_WORDING = Object.freeze({
|
|
|
23
25
|
searching_wider: 'Checking more stores for this…',
|
|
24
26
|
});
|
|
25
27
|
/** The handlers that ask Agentcard to stream a turn. */
|
|
26
|
-
const STREAM_HANDLERS = ['onProgress', 'onStatus', 'onFound', 'onCatalog', 'onSegment', 'onAck', 'onCart', 'onDelta', 'onAvailability', 'onReplyWritten', 'onMerchantStatus'];
|
|
28
|
+
const STREAM_HANDLERS = ['onProgress', 'onStatus', 'onFound', 'onCatalog', 'onSegment', 'onAck', 'onCart', 'onDelta', 'onAvailability', 'onReplyWritten', 'onMerchantStatus', 'onRecheck', 'onFreshness', 'onStoreCheck'];
|
|
27
29
|
const PROGRESS_STAGES = new Set(['searching', 'building_cart', 'checking_out', 'working']);
|
|
28
30
|
export const DEFAULT_BASE_URL = 'https://api.agentcard.sh';
|
|
29
31
|
/** The wait for one purchase turn: past Agentcard's own 110-second budget, so a slow turn answers `turn_timeout` rather than aborting here. */
|
|
@@ -108,6 +110,45 @@ export class AgentcardApiError extends Error {
|
|
|
108
110
|
}
|
|
109
111
|
const str = (v) => (typeof v === 'string' && v !== '' ? v : null);
|
|
110
112
|
const num = (v) => (typeof v === 'number' && Number.isFinite(v) ? v : null);
|
|
113
|
+
const QUOTE_KINDS = new Set(['flight', 'event_ticket', 'hotel', 'rental_car', 'restaurant_table', 'store_cart']);
|
|
114
|
+
const FRESHNESS = new Set(['same', 'changed', 'gone']);
|
|
115
|
+
const FRESH_STATUS = { same: 'confirmed', changed: 'changed', gone: 'gone' };
|
|
116
|
+
/** One item of a `freshness` event or a finished `recheck`, or null when it is not one. */
|
|
117
|
+
function freshnessOf(data, conversationId) {
|
|
118
|
+
const ref = str(data.ref);
|
|
119
|
+
const kind = str(data.kind);
|
|
120
|
+
const name = str(data.name);
|
|
121
|
+
const freshness = str(data.freshness);
|
|
122
|
+
if (!ref || !kind || !QUOTE_KINDS.has(kind) || !name || !freshness || !FRESHNESS.has(freshness))
|
|
123
|
+
return null;
|
|
124
|
+
return {
|
|
125
|
+
conversationId, ref, kind: kind, name, seller: str(data.seller),
|
|
126
|
+
freshness: freshness, status: FRESH_STATUS[freshness], text: str(data.text) ?? '',
|
|
127
|
+
previousCents: num(data.previous_cents), amountCents: num(data.amount_cents), currency: str(data.currency), checkedAt: str(data.checked_at) ?? '',
|
|
128
|
+
};
|
|
129
|
+
}
|
|
130
|
+
/** A `recheck` event as the app reads it. */
|
|
131
|
+
function recheckOf(data, conversationId) {
|
|
132
|
+
const items = (Array.isArray(data.items) ? data.items : []).flatMap((raw) => {
|
|
133
|
+
if (!raw || typeof raw !== 'object')
|
|
134
|
+
return [];
|
|
135
|
+
const i = raw;
|
|
136
|
+
const done = freshnessOf(i, conversationId);
|
|
137
|
+
if (done)
|
|
138
|
+
return [done];
|
|
139
|
+
const ref = str(i.ref);
|
|
140
|
+
const kind = str(i.kind);
|
|
141
|
+
const name = str(i.name);
|
|
142
|
+
// Only a started re-check's item is still checking: a finished one this version cannot read is left out.
|
|
143
|
+
if (data.phase !== 'started' || !ref || !kind || !QUOTE_KINDS.has(kind) || !name)
|
|
144
|
+
return [];
|
|
145
|
+
return [{ ref, kind: kind, name, seller: str(i.seller), text: str(i.text) ?? '', status: 'checking' }];
|
|
146
|
+
});
|
|
147
|
+
return {
|
|
148
|
+
conversationId, phase: data.phase, reason: data.reason === 'commit' ? 'commit' : 'return',
|
|
149
|
+
text: str(data.text), items, unread: Array.isArray(data.unread) ? data.unread.filter((u) => typeof u === 'string') : [],
|
|
150
|
+
};
|
|
151
|
+
}
|
|
111
152
|
const bool = (v) => (typeof v === 'boolean' ? v : null);
|
|
112
153
|
/**
|
|
113
154
|
* A product page's address: an absolute http or https URL, serialized the way
|
|
@@ -251,7 +292,8 @@ function partyOf(value) {
|
|
|
251
292
|
if (adults === null || children === null || totalCents === null || adults + children < 2)
|
|
252
293
|
return {};
|
|
253
294
|
const each = Array.isArray(value.perPassengerCents) ? value.perPassengerCents.map(num) : null;
|
|
254
|
-
|
|
295
|
+
const infants = num(value.infants);
|
|
296
|
+
return { party: { adults, children, ...(infants !== null && infants > 0 && infants <= children ? { infants } : {}), totalCents, ...(each && each.length === adults + children && each.every((c) => c !== null) ? { perPassengerCents: each } : {}) } };
|
|
255
297
|
}
|
|
256
298
|
function catalogOf(v) {
|
|
257
299
|
if (!isRecord(v))
|
|
@@ -276,6 +318,8 @@ function catalogItemsOf(v) {
|
|
|
276
318
|
store: storeOf(i.store),
|
|
277
319
|
url: pageUrl(i.url),
|
|
278
320
|
...availabilityField(i),
|
|
321
|
+
ships_to_you: i.ships_to_you === 'yes' || i.ships_to_you === 'no' ? i.ships_to_you : 'unknown',
|
|
322
|
+
ships_note: str(i.ships_note),
|
|
279
323
|
...fulfilledByField(i),
|
|
280
324
|
...cardSizeField(i),
|
|
281
325
|
};
|
|
@@ -416,6 +460,7 @@ function conversationOf(body) {
|
|
|
416
460
|
})) : [],
|
|
417
461
|
carts: cartsOf(body.carts),
|
|
418
462
|
latest_reply: latestReplyOf(body.latest_reply),
|
|
463
|
+
trip: tripOf(body.trip),
|
|
419
464
|
raw: body,
|
|
420
465
|
};
|
|
421
466
|
}
|
|
@@ -535,7 +580,7 @@ async function eachEvent(res, onEvent) {
|
|
|
535
580
|
}
|
|
536
581
|
/**
|
|
537
582
|
* A streamed purchase turn, read to its `envelope`: the same answer a JSON turn returns, with its
|
|
538
|
-
* status in `http_status`. Each `progress`, `status`, `delta`, `catalog`, `find`, `segment`, `reply_written`, `cart`, `venue_availability` and `
|
|
583
|
+
* status in `http_status`. Each `progress`, `status`, `delta`, `catalog`, `find`, `segment`, `reply_written`, `cart`, `venue_availability`, `merchant` and `store_check` goes to its handler
|
|
539
584
|
* on the way, and any other event, such as a `ping` or a `tool`, is read past. A stream that breaks or
|
|
540
585
|
* ends before its envelope, once it named the conversation, reads as a turn past its budget
|
|
541
586
|
* (`turn_timeout`), so the tool reads the conversation for what the turn left instead of losing it.
|
|
@@ -620,6 +665,14 @@ async function readStreamedTurn(res, stream, timeoutMs) {
|
|
|
620
665
|
else if (event === 'ack' && typeof data.text === 'string' && data.text.trim() !== '' && stream.onAck) {
|
|
621
666
|
deliver(() => stream.onAck({ conversationId: named, text: data.text }));
|
|
622
667
|
}
|
|
668
|
+
else if (event === 'recheck' && stream.onRecheck && (data.phase === 'started' || data.phase === 'finished')) {
|
|
669
|
+
const recheck = recheckOf(data, named);
|
|
670
|
+
deliver(() => stream.onRecheck(recheck));
|
|
671
|
+
}
|
|
672
|
+
else if (event === 'freshness' && stream.onFreshness && freshnessOf(data, named)) {
|
|
673
|
+
const one = freshnessOf(data, named);
|
|
674
|
+
deliver(() => stream.onFreshness(one));
|
|
675
|
+
}
|
|
623
676
|
else if (event === 'venue_availability' && stream.onAvailability && str(data.conversation_id) && Array.isArray(data.items)) {
|
|
624
677
|
const items = data.items.filter(isRecord).flatMap((i) => {
|
|
625
678
|
const id = str(i.id);
|
|
@@ -629,6 +682,18 @@ async function readStreamedTurn(res, stream, timeoutMs) {
|
|
|
629
682
|
if (items.length)
|
|
630
683
|
deliver(() => stream.onAvailability({ conversationId: data.conversation_id, items }));
|
|
631
684
|
}
|
|
685
|
+
else if (event === 'store_check' && stream.onStoreCheck && str(data.conversation_id) && isRecord(data.item)) {
|
|
686
|
+
const i = data.item;
|
|
687
|
+
const cardId = str(i.id);
|
|
688
|
+
const store = str(i.store);
|
|
689
|
+
if (cardId && store && typeof i.state === 'string' && STORE_CHECK_STATES.has(i.state)) {
|
|
690
|
+
const update = {
|
|
691
|
+
conversationId: data.conversation_id, cardId, store, storeName: str(i.store_name) ?? store,
|
|
692
|
+
state: i.state, priceCents: typeof i.price_cents === 'number' ? i.price_cents : null,
|
|
693
|
+
};
|
|
694
|
+
deliver(() => stream.onStoreCheck(update));
|
|
695
|
+
}
|
|
696
|
+
}
|
|
632
697
|
else if (event === 'merchant' && stream.onMerchantStatus) {
|
|
633
698
|
const update = merchantStatusOf(data);
|
|
634
699
|
if (update)
|
package/dist/definition.js
CHANGED
|
@@ -45,7 +45,7 @@ export const BUY_TOOL_RULES = [
|
|
|
45
45
|
'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?".',
|
|
46
46
|
'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.',
|
|
47
47
|
'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)".',
|
|
48
|
-
'When `next_action` is `confirm`, ask in one short message: the items,
|
|
48
|
+
'When `next_action` is `confirm`, ask in one short message: the items, each at its price before fees as the cart lists it, saying so when that is not the price its card showed, the total said as "with fees", and to say yes to place it, such as "1 carolina black socks pack from walmart, $9.34. $9.57 with fees. say yes to place it". Never give the total as an item\'s price. Leave out the fees one by one, the hold, and the address unless the shopper just changed it.',
|
|
49
49
|
'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".',
|
|
50
50
|
'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.',
|
|
51
51
|
'Never tell the shopper to remove, delete or replace a card or to change a setting, even when `reply` says to.',
|
|
@@ -54,7 +54,7 @@ export const BUY_TOOL_RULES = [
|
|
|
54
54
|
'Write every sentence in your own style, casing included, even where `reply` is written another way.',
|
|
55
55
|
"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, Best Buy and thousands more stores, so name those four and say \"and thousands more stores\" every time you name the stores you shop, in a reply about one kind of thing too, such as \"travel gear from Amazon, Target and thousands more stores\": never let the four read as the whole list, never say \"plenty more\", \"many more\" or \"most online stores\", and never give a number of stores. Name no other store unless a result showed it.",
|
|
56
56
|
"Never tell the shopper you can't render or show Markdown, a table or other formatting, and never say you keep to plain text: how your words look is your app's to decide.",
|
|
57
|
-
'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.',
|
|
57
|
+
'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, $11.88, about $12.17 with fees before delivery and tax" and then "next, sign in to doordash with the link so i can place it": each item at its price before fees as the cart lists it, saying so when that is not the price its card showed, and one total said as "with fees", never the total as an item\'s price, 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.',
|
|
58
58
|
'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".',
|
|
59
59
|
'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.',
|
|
60
60
|
'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.',
|
package/dist/events.d.ts
CHANGED
|
@@ -3,7 +3,7 @@
|
|
|
3
3
|
* name. The stream (`agentcard.listen`) and a webhook endpoint carry the same types, with the same
|
|
4
4
|
* envelope and the same ids. A test holds this list to the backend's catalog.
|
|
5
5
|
*/
|
|
6
|
-
export declare const AGENTCARD_EVENT_TYPES: readonly ['card.created', 'card.updated', 'card.closed', 'cardholder.created', 'cardholder.updated', 'transaction.authorized', 'transaction.declined', 'transaction.cleared', 'transaction.voided', 'balance.low', 'cardholder_onboarding_session.completed', 'platform_connect_session.completed', 'merchant.connected', 'order.placed', 'order.confirmed', 'order.failed', 'order.updated', 'purchase.message', 'connection.created', 'connected_card.updated', 'vault.card_stored', 'vault.session_linked', 'vault.session_card_picked', 'vault.payment_permission.updated', 'action.confirmed', 'action.done', 'checkout_authorization.approved', 'checkout_authorization.submitted', 'checkout_authorization.declined', 'checkout_authorization.cancelled', 'checkout_authorization.expired', 'checkout_authorization.duplicate_charge', 'checkout_authorization.refused', 'checkout_authorization.watched', 'checkout_authorization.amount_mismatch', 'checkout_authorization.settled', 'checkout_authorization.settlement_unconfirmed', 'checkout_authorization.outcome_reported', 'card_flow.started', 'card_flow.failed', 'transfer.approved', 'transfer.initiated', 'transfer.completed', 'transfer.failed', 'transfer.released', 'wallet.funded', 'user_wallet.funding_detected', 'user_wallet.funded', 'wallet.balance.low', 'recovery.requested', 'recovery.completed', 'recovery.rejected', 'wallet.withdrawal.initiated', 'wallet.withdrawal.completed', 'wallet.withdrawal.failed', 'identity.verification.updated', 'approval.requested', 'reward.earned', 'reward.reversed', 'wallet_link.opened'];
|
|
6
|
+
export declare const AGENTCARD_EVENT_TYPES: readonly ['card.created', 'card.updated', 'card.closed', 'cardholder.created', 'cardholder.updated', 'transaction.authorized', 'transaction.declined', 'transaction.cleared', 'transaction.voided', 'balance.low', 'cardholder_onboarding_session.completed', 'platform_connect_session.completed', 'merchant.connected', 'order.placed', 'order.confirmed', 'order.failed', 'order.updated', 'purchase.message', 'connection.created', 'connected_card.updated', 'vault.card_stored', 'vault.session_linked', 'vault.session_card_picked', 'vault.payment_permission.updated', 'action.confirmed', 'action.done', 'checkout_authorization.approved', 'checkout_authorization.submitted', 'checkout_authorization.declined', 'checkout_authorization.cancelled', 'checkout_authorization.expired', 'checkout_authorization.duplicate_charge', 'checkout_authorization.refused', 'checkout_authorization.watched', 'checkout_authorization.amount_mismatch', 'checkout_authorization.settled', 'checkout_authorization.settlement_unconfirmed', 'checkout_authorization.outcome_reported', 'card_flow.started', 'card_flow.failed', 'transfer.approved', 'transfer.initiated', 'transfer.completed', 'transfer.failed', 'transfer.released', 'wallet.funded', 'user_wallet.funding_detected', 'user_wallet.funded', 'wallet.balance.low', 'recovery.requested', 'recovery.completed', 'recovery.rejected', 'wallet.withdrawal.initiated', 'wallet.withdrawal.completed', 'wallet.withdrawal.failed', 'identity.verification.updated', 'approval.requested', 'reward.earned', 'reward.reversed', 'wallet_link.opened', 'plan.updated', 'plan.item.updated'];
|
|
7
7
|
/** One event type Agentcard sends. A type Agentcard adds later still reads as its own name. */
|
|
8
8
|
export type AgentcardEventType = (typeof AGENTCARD_EVENT_TYPES)[number];
|
|
9
9
|
/** Whether a filter entry names something Agentcard sends: an event type, a family such as `order.*`, or `*`. */
|
package/dist/events.js
CHANGED
|
@@ -64,6 +64,8 @@ export const AGENTCARD_EVENT_TYPES = [
|
|
|
64
64
|
'reward.earned',
|
|
65
65
|
'reward.reversed',
|
|
66
66
|
'wallet_link.opened',
|
|
67
|
+
'plan.updated',
|
|
68
|
+
'plan.item.updated',
|
|
67
69
|
];
|
|
68
70
|
const KNOWN = new Set(AGENTCARD_EVENT_TYPES);
|
|
69
71
|
const FAMILIES = new Set(AGENTCARD_EVENT_TYPES.map((t) => `${t.split('.')[0]}.*`));
|
package/dist/index.d.ts
CHANGED
|
@@ -4,7 +4,7 @@ export { readShopperMessage, isStale, SHOPPER_EVENT_TYPES, NEXT_STEP_KINDS, type
|
|
|
4
4
|
export { ORDER_STEPS, updateOrder, arrivalText, type ShopperOrder, type OrderStep, type OrderPathStep, type OrderStepState, type ArrivalTextOptions, } from './order.js';
|
|
5
5
|
export { agentcardWebhook, purchaseAgentWebhook, type AgentcardWebhookOptions, type AgentcardWebhookHandler, type PurchaseAgentWebhookOptions, type WebhookFetchRequest, type WebhookRequest, type WebhookResponse, } from './handler.js';
|
|
6
6
|
export { searchAt, isMerchantStatusAt, merchantStatusLine, searchStatusLines, type BuySearchStatus, type BuyMerchantStatus, type BuyMerchantSearchStatus, type BuyMerchantStatusUpdate, } from './search-status.js';
|
|
7
|
-
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, STATUS_WORDING, type BuySegmentUpdate, type BuyCartUpdate, type BuyDeltaUpdate, type BuyStreamHandlers, type Shopper, type BuyLastAnswer, type BuyRequest, type BuyDeliveryAddress, type BuyTurn, type BuyStatus, type BuyCart, type BuyCartBuilding, type BuyCartLine, type BuyCartFeeLine, type BuyCartDiscount, type BuyCartHold, type BuyStore, type BuyCatalog, type BuyCatalogItem, type BuyFlightItinerary, type BuyFlightParty, type BuyPaymentSource, type BuyConversation, type BuyLatestReply, type BuyLastCheckout, type BuyOrder, type VaultLink, type BuyNextAction, type BuyPick, type BuyStoreChoice, type BuyStorePick, type BuyStores, type BuyChoice, type BuyCodeRequest, type BuyChoiceGroup, type BuyChoiceDelivery, type BuyChoiceOption, } from './client.js';
|
|
7
|
+
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 BuyStoreCheckUpdate, type BuyFoundUpdate, type BuyTurnStatus, STATUS_WORDING, type BuySegmentUpdate, type BuyRecheck, type BuyFreshness, type BuyFreshnessState, type BuyCartUpdate, type BuyDeltaUpdate, type BuyStreamHandlers, type Shopper, type BuyLastAnswer, type BuyRequest, type BuyDeliveryAddress, type BuyTurn, type BuyStatus, type BuyCart, type BuyCartBuilding, type BuyCartLine, type BuyCartFeeLine, type BuyCartDiscount, type BuyCartHold, type BuyStore, type BuyCatalog, type BuyCatalogItem, type BuyFlightItinerary, type BuyFlightParty, type BuyPaymentSource, type BuyConversation, type BuyLatestReply, type BuyLastCheckout, type BuyOrder, type VaultLink, type BuyNextAction, type BuyPick, type BuyStoreChoice, type BuyStorePick, type BuyStores, type BuyChoice, type BuyCodeRequest, type BuyChoiceGroup, type BuyChoiceDelivery, type BuyChoiceOption, } from './client.js';
|
|
8
8
|
export { BUY_TOOL_NAME, BUY_TOOL_DESCRIPTION, buyToolDefinition, parseBuyToolInput, type BuyToolDefinition, type BuyToolInput, type BuyToolInputSchema, type BuyToolInputCheck, type BuyToolStandardSchema, type OpenAIBuyTool, } from './definition.js';
|
|
9
9
|
export { toMessages, messageStream, typingDelay, type MessagePace, type MessageStream, type MessageStreamOptions, } from './messages.js';
|
|
10
10
|
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';
|
|
@@ -21,5 +21,6 @@ export { turnStream, readTurn, TURN_RECORD_TTL_SECONDS, type TurnStream, type Tu
|
|
|
21
21
|
export { TURN_RECOVERY_ON, isTurnId, type TurnEvent, type TurnRecord, type TurnRecordStore } from './turn-record.js';
|
|
22
22
|
export type { BuyLateResults, BuyStoreWait } from './store-wait.js';
|
|
23
23
|
export { planOf, type BuyPlan, type BuyPlanItem, type BuyPlanItemState, type BuyPlanStatus } from './plan.js';
|
|
24
|
+
export { tripOf, type BuyTrip, type TripLane, type TripLaneName, type TripLaneResult, type TripLaneStatus } from './trip.js';
|
|
24
25
|
export { fulfilledByLine, fulfilledByOf, type BuyFulfilledBy, type BuyFulfilledByKind } from './fulfilled-by.js';
|
|
25
26
|
export { cardSizeOf, type BuyCardSize } from './card-size.js';
|
package/dist/index.js
CHANGED
|
@@ -20,5 +20,6 @@ export { AGENTCARD_EVENT_TYPES, } from './events.js';
|
|
|
20
20
|
export { turnStream, readTurn, TURN_RECORD_TTL_SECONDS } from './turn-stream.js';
|
|
21
21
|
export { TURN_RECOVERY_ON, isTurnId } from './turn-record.js';
|
|
22
22
|
export { planOf } from './plan.js';
|
|
23
|
+
export { tripOf } from './trip.js';
|
|
23
24
|
export { fulfilledByLine, fulfilledByOf } from './fulfilled-by.js';
|
|
24
25
|
export { cardSizeOf } from './card-size.js';
|
package/dist/model-view.d.ts
CHANGED
|
@@ -213,8 +213,9 @@ export declare function productPrice(product: {
|
|
|
213
213
|
};
|
|
214
214
|
}): string | null;
|
|
215
215
|
/**
|
|
216
|
-
* Who a party's flight price covers, as every app shows it after the price: `for 2`,
|
|
217
|
-
* children fly. `$1,610 for 2` is the price of both
|
|
216
|
+
* Who a party's flight price covers, as every app shows it after the price: `for 2`, `for 2 adults and 1 child` when
|
|
217
|
+
* children fly, and `for 2 adults, 1 child, 1 infant` when an infant flies on a lap. `$1,610 for 2` is the price of both
|
|
218
|
+
* seats together. Every traveller is named, so a card never reads as a price for fewer people than fly.
|
|
218
219
|
*/
|
|
219
220
|
export declare function flightPartyWords(party: BuyFlightParty): string;
|
|
220
221
|
export declare function money(minor: number | null | undefined, currency: string | null | undefined): string | null;
|
package/dist/model-view.js
CHANGED
|
@@ -115,13 +115,21 @@ export function productPrice(product) {
|
|
|
115
115
|
return price !== null && product.priceFrom ? `from ${price}` : price;
|
|
116
116
|
}
|
|
117
117
|
/**
|
|
118
|
-
* Who a party's flight price covers, as every app shows it after the price: `for 2`,
|
|
119
|
-
* children fly. `$1,610 for 2` is the price of both
|
|
118
|
+
* Who a party's flight price covers, as every app shows it after the price: `for 2`, `for 2 adults and 1 child` when
|
|
119
|
+
* children fly, and `for 2 adults, 1 child, 1 infant` when an infant flies on a lap. `$1,610 for 2` is the price of both
|
|
120
|
+
* seats together. Every traveller is named, so a card never reads as a price for fewer people than fly.
|
|
120
121
|
*/
|
|
121
122
|
export function flightPartyWords(party) {
|
|
122
123
|
if (party.children === 0)
|
|
123
124
|
return `for ${party.adults}`;
|
|
124
|
-
|
|
125
|
+
const lap = Math.min(Math.max(0, party.infants ?? 0), party.children);
|
|
126
|
+
const kids = party.children - lap;
|
|
127
|
+
const groups = [
|
|
128
|
+
`${party.adults} adult${party.adults === 1 ? '' : 's'}`,
|
|
129
|
+
...(kids ? [`${kids} ${kids === 1 ? 'child' : 'children'}`] : []),
|
|
130
|
+
...(lap ? [`${lap} infant${lap === 1 ? '' : 's'}`] : []),
|
|
131
|
+
];
|
|
132
|
+
return `for ${groups.length === 2 ? groups.join(' and ') : groups.join(', ')}`;
|
|
125
133
|
}
|
|
126
134
|
export function money(minor, currency) {
|
|
127
135
|
if (typeof minor !== 'number' || !Number.isFinite(minor))
|
package/dist/trip.d.ts
ADDED
|
@@ -0,0 +1,50 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* A trip Agentcard is planning, as a conversation read carries it (`BuyConversation.trip`): the place, the dates and the
|
|
3
|
+
* party, and each search Agentcard runs for it in the background, flights first. An app shows each lane as its results
|
|
4
|
+
* arrive and shows `checking` beside a lane until `done`; it never presents a lane's results as the full list before then.
|
|
5
|
+
* `fulfilled_by` on a result names who the shopper books with ("Korean Air", "Marriott"), when Agentcard knows it.
|
|
6
|
+
*/
|
|
7
|
+
export type TripLaneName = 'flights' | 'hotels' | 'car' | 'activities' | 'payment';
|
|
8
|
+
export type TripLaneStatus = 'waiting' | 'queued' | 'searching' | 'confirmed' | 'no_results' | 'failed' | 'timed_out' | 'cancelled';
|
|
9
|
+
export interface TripLaneResult {
|
|
10
|
+
product_id: string;
|
|
11
|
+
title: string;
|
|
12
|
+
price_cents: number | null;
|
|
13
|
+
currency: string | null;
|
|
14
|
+
fulfilled_by: {
|
|
15
|
+
name: string;
|
|
16
|
+
kind: string;
|
|
17
|
+
} | null;
|
|
18
|
+
}
|
|
19
|
+
export interface TripLane {
|
|
20
|
+
lane: TripLaneName;
|
|
21
|
+
status: TripLaneStatus;
|
|
22
|
+
/** False while the lane waits, queues or searches. */
|
|
23
|
+
done: boolean;
|
|
24
|
+
/** The quiet line to show beside the lane until it is done ("still checking hotels…"); null once it is. */
|
|
25
|
+
checking: string | null;
|
|
26
|
+
/** Why the lane waits or how it ended, in a word ("needs_origin"), or null. */
|
|
27
|
+
why: string | null;
|
|
28
|
+
results: number;
|
|
29
|
+
/** The lane's results, ordered by what the shopper said matters, at most 8. */
|
|
30
|
+
items: TripLaneResult[];
|
|
31
|
+
/** The lane's last answer to a stated preference: re-ranked, searched again narrowly, or both, and why. */
|
|
32
|
+
decision: {
|
|
33
|
+
action: 'rerank' | 'narrow' | 'both';
|
|
34
|
+
why: string[];
|
|
35
|
+
} | null;
|
|
36
|
+
/** Hotels: false when the picked flight lands on another day than these hotels were searched for. */
|
|
37
|
+
fits_flight?: boolean | null;
|
|
38
|
+
/** The card check: true when the shopper's card pays without stopping at checkout. */
|
|
39
|
+
ready?: boolean;
|
|
40
|
+
}
|
|
41
|
+
export interface BuyTrip {
|
|
42
|
+
place: string | null;
|
|
43
|
+
check_in: string | null;
|
|
44
|
+
check_out: string | null;
|
|
45
|
+
travelers: number | null;
|
|
46
|
+
origin: string | null;
|
|
47
|
+
lanes: TripLane[];
|
|
48
|
+
}
|
|
49
|
+
/** The trip a conversation read carries, or null when it plans none or the API is older. */
|
|
50
|
+
export declare function tripOf(value: unknown): BuyTrip | null;
|
package/dist/trip.js
ADDED
|
@@ -0,0 +1,31 @@
|
|
|
1
|
+
const isRecord = (v) => typeof v === 'object' && v !== null && !Array.isArray(v);
|
|
2
|
+
const str = (v) => (typeof v === 'string' && v !== '' ? v : null);
|
|
3
|
+
const num = (v) => (typeof v === 'number' && Number.isFinite(v) ? v : null);
|
|
4
|
+
const LANES = ['flights', 'hotels', 'car', 'activities', 'payment'];
|
|
5
|
+
const STATUSES = ['waiting', 'queued', 'searching', 'confirmed', 'no_results', 'failed', 'timed_out', 'cancelled'];
|
|
6
|
+
const ACTIONS = ['rerank', 'narrow', 'both'];
|
|
7
|
+
const oneOf = (allowed, v) => (typeof v === 'string' && allowed.includes(v) ? v : null);
|
|
8
|
+
/** The trip a conversation read carries, or null when it plans none or the API is older. */
|
|
9
|
+
export function tripOf(value) {
|
|
10
|
+
if (!isRecord(value) || !Array.isArray(value.lanes))
|
|
11
|
+
return null;
|
|
12
|
+
return {
|
|
13
|
+
place: str(value.place), check_in: str(value.check_in), check_out: str(value.check_out), travelers: num(value.travelers), origin: str(value.origin),
|
|
14
|
+
// A lane this SDK does not know (a newer API's) is left out, and a status it does not know reads as still searching.
|
|
15
|
+
lanes: value.lanes.filter(isRecord).filter((l) => oneOf(LANES, l.lane) !== null).map((l) => ({
|
|
16
|
+
lane: oneOf(LANES, l.lane),
|
|
17
|
+
status: oneOf(STATUSES, l.status) ?? 'searching',
|
|
18
|
+
done: l.done === true,
|
|
19
|
+
checking: l.done === true ? null : str(l.checking),
|
|
20
|
+
why: str(l.why),
|
|
21
|
+
results: num(l.results) ?? 0,
|
|
22
|
+
items: Array.isArray(l.items) ? l.items.filter(isRecord).map((i) => ({
|
|
23
|
+
product_id: str(i.product_id) ?? '', title: str(i.title) ?? '', price_cents: num(i.price_cents), currency: str(i.currency),
|
|
24
|
+
fulfilled_by: isRecord(i.fulfilled_by) && str(i.fulfilled_by.name) ? { name: str(i.fulfilled_by.name), kind: str(i.fulfilled_by.kind) ?? 'seller' } : null,
|
|
25
|
+
})) : [],
|
|
26
|
+
decision: isRecord(l.decision) && oneOf(ACTIONS, l.decision.action) ? { action: oneOf(ACTIONS, l.decision.action), why: Array.isArray(l.decision.why) ? l.decision.why.filter((w) => typeof w === 'string') : [] } : null,
|
|
27
|
+
...('fits_flight' in l ? { fits_flight: typeof l.fits_flight === 'boolean' ? l.fits_flight : null } : {}),
|
|
28
|
+
...('ready' in l ? { ready: l.ready === true } : {}),
|
|
29
|
+
})),
|
|
30
|
+
};
|
|
31
|
+
}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@agent-cards/commerce-sdk",
|
|
3
|
-
"version": "0.18.
|
|
3
|
+
"version": "0.18.11",
|
|
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
|
"homepage": "https://opencommerce.sh",
|
|
6
6
|
"type": "module",
|