@agent-cards/commerce-sdk 0.18.8 → 0.18.10
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 +32 -0
- package/dist/buy-tool.js +73 -8
- package/dist/card-size.d.ts +22 -0
- package/dist/card-size.js +23 -0
- package/dist/client.d.ts +92 -0
- package/dist/client.js +78 -2
- package/dist/definition.js +2 -2
- package/dist/events.d.ts +1 -1
- package/dist/events.js +2 -0
- package/dist/index.d.ts +4 -2
- package/dist/index.js +3 -1
- package/dist/model-view.d.ts +9 -1
- package/dist/model-view.js +11 -0
- 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
|
@@ -31,8 +31,10 @@
|
|
|
31
31
|
* messages and links as one sequence, in the order they happened, and spaces
|
|
32
32
|
* the messages like someone texting.
|
|
33
33
|
*/
|
|
34
|
+
import type { BuyCardSize } from './card-size.js';
|
|
34
35
|
import type { BuyPlan } from './plan.js';
|
|
35
36
|
import type { BuyMerchantStatusUpdate, BuySearchStatus } from './search-status.js';
|
|
37
|
+
import type { BuyFreshness, BuyRecheck, BuyStoreCheckUpdate } from './client.js';
|
|
36
38
|
import type { BuyFulfilledBy } from './fulfilled-by.js';
|
|
37
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';
|
|
38
40
|
import { type BuyToolDefinition, type BuyToolInput, type BuyToolInputSchema, type BuyToolStandardSchema, type OpenAIBuyTool } from './definition.js';
|
|
@@ -97,6 +99,8 @@ export interface BuyToolLink {
|
|
|
97
99
|
* `{ name: 'StubHub', kind: 'marketplace', line: 'via StubHub' }`. Show `line` quietly under the price. Absent when none.
|
|
98
100
|
*/
|
|
99
101
|
fulfilledBy?: BuyFulfilledBy;
|
|
102
|
+
/** On a product link: the shopper's size as the store's read found it. Show `line` under the price when it is not null. */
|
|
103
|
+
shopperSize?: BuyCardSize;
|
|
100
104
|
/**
|
|
101
105
|
* On a product link: what taking the product does, so a button can say it. `reserve` is a table the
|
|
102
106
|
* shopper reserves, `book` a flight, room, class or ticket they book, and `buy` anything they buy.
|
|
@@ -134,6 +138,10 @@ export interface BuyToolLink {
|
|
|
134
138
|
* Absent on every other card, such as a retail product's.
|
|
135
139
|
*/
|
|
136
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;
|
|
137
145
|
}
|
|
138
146
|
/** A store the answer's reply presents to choose from. */
|
|
139
147
|
export interface BuyToolStore {
|
|
@@ -184,8 +192,14 @@ export interface BuyToolProduct {
|
|
|
184
192
|
linkSent: boolean;
|
|
185
193
|
/** For a place to book: whether it has room at the asked time. Absent on a product with no such question. */
|
|
186
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;
|
|
187
199
|
/** Who sells or books it, where that is not the store it names (`via StubHub`); absent when Agentcard named none. */
|
|
188
200
|
fulfilledBy?: BuyFulfilledBy;
|
|
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. */
|
|
202
|
+
shopperSize?: BuyCardSize;
|
|
189
203
|
}
|
|
190
204
|
/** One answer of the buy tool, whole, for your code. Hand it to `forModel` before a model reads it. */
|
|
191
205
|
export interface BuyToolResult {
|
|
@@ -407,6 +421,18 @@ export interface BuyToolOptions extends BuyStoreWait {
|
|
|
407
421
|
* search starts, then what it found. One that throws costs nothing but the call to it.
|
|
408
422
|
*/
|
|
409
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>;
|
|
410
436
|
/**
|
|
411
437
|
* Hears the products a call's search found, the moment the search answers and before Agentcard writes its reply,
|
|
412
438
|
* with the conversation, when the search ran (`asOf`, as the answer's `catalog.as_of` names it) and the store's name.
|
|
@@ -636,6 +662,12 @@ export interface BuyToolTurnOptions extends BuyStoreWait {
|
|
|
636
662
|
onAvailability?: BuyToolOptions['onAvailability'];
|
|
637
663
|
/** Hears each store this turn's searches ask, as the tool's `onMerchantStatus` does. */
|
|
638
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'];
|
|
639
671
|
/** Hears the products each search of this turn's calls found, the moment it answers, as the tool's `onProducts` does. */
|
|
640
672
|
onProducts?: BuyToolOptions['onProducts'];
|
|
641
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 } : {}) };
|
|
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,7 +282,10 @@ 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 } : {}),
|
|
288
|
+
...(item.shopper_size ? { shopperSize: item.shopper_size } : {}),
|
|
282
289
|
}));
|
|
283
290
|
}
|
|
284
291
|
/** The stores an answer presents, as the tool reads them. */
|
|
@@ -770,7 +777,7 @@ export function buyTool(options) {
|
|
|
770
777
|
if (o.stream !== undefined && o.stream !== false)
|
|
771
778
|
throw new TypeError('Pass stream as false to wait for each whole answer, or leave it out.');
|
|
772
779
|
const onProgress = o.onProgress;
|
|
773
|
-
for (const key of ['onStatus', 'onFound', 'onAvailability', 'onMerchantStatus']) {
|
|
780
|
+
for (const key of ['onStatus', 'onFound', 'onAvailability', 'onMerchantStatus', 'onStoreCheck', 'onRecheck', 'onFreshness']) {
|
|
774
781
|
if (o[key] !== undefined && typeof o[key] !== 'function')
|
|
775
782
|
throw new TypeError(`Pass ${key} as a function, or leave it out.`);
|
|
776
783
|
}
|
|
@@ -852,6 +859,9 @@ export function buyTool(options) {
|
|
|
852
859
|
onFound: o.onFound,
|
|
853
860
|
onAvailability: o.onAvailability,
|
|
854
861
|
onMerchantStatus: o.onMerchantStatus,
|
|
862
|
+
onStoreCheck: o.onStoreCheck,
|
|
863
|
+
onRecheck: o.onRecheck,
|
|
864
|
+
onFreshness: o.onFreshness,
|
|
855
865
|
onProducts: o.onProducts,
|
|
856
866
|
onText: o.onText,
|
|
857
867
|
texting: textingByDefault,
|
|
@@ -1920,9 +1930,16 @@ export function buyTool(options) {
|
|
|
1920
1930
|
},
|
|
1921
1931
|
}
|
|
1922
1932
|
: {}),
|
|
1923
|
-
...(early || t.onProducts
|
|
1933
|
+
...(early || t.onProducts || t.onStoreCheck
|
|
1924
1934
|
? {
|
|
1925
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
|
+
}
|
|
1926
1943
|
try {
|
|
1927
1944
|
await t.onProducts?.(productsOfCatalog(update.catalog), { conversationId: update.conversationId, asOf: update.asOf, merchantName: update.catalog.merchant_name });
|
|
1928
1945
|
}
|
|
@@ -1933,8 +1950,9 @@ export function buyTool(options) {
|
|
|
1933
1950
|
const keepMore = () => { if (t.onFound && early)
|
|
1934
1951
|
moreFound = { conversationId: update.conversationId, links: productsOfCatalog({ ...update.catalog, items: update.more }).map(productLink) }; };
|
|
1935
1952
|
if (!early) {
|
|
1936
|
-
// Without early cards nothing goes out here, but a store that answered after the reply still joins the
|
|
1937
|
-
|
|
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)
|
|
1938
1956
|
lateCards.push(...productsOfCatalog(update.catalog));
|
|
1939
1957
|
return;
|
|
1940
1958
|
}
|
|
@@ -1977,6 +1995,50 @@ export function buyTool(options) {
|
|
|
1977
1995
|
},
|
|
1978
1996
|
}
|
|
1979
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
|
+
: {}),
|
|
1980
2042
|
...(t.onText || writtenText
|
|
1981
2043
|
? {
|
|
1982
2044
|
onSegment: async ({ text, conversationId }) => {
|
|
@@ -2331,7 +2393,7 @@ export function buyTool(options) {
|
|
|
2331
2393
|
const own = options ?? {};
|
|
2332
2394
|
if (typeof own !== 'object')
|
|
2333
2395
|
throw new TypeError('Pass the turn options as an object, or leave them out.');
|
|
2334
|
-
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']) {
|
|
2335
2397
|
if (own[key] !== undefined && typeof own[key] !== 'function')
|
|
2336
2398
|
throw new TypeError(`Pass ${key} to the turn as a function, or leave it out.`);
|
|
2337
2399
|
}
|
|
@@ -2416,6 +2478,9 @@ export function buyTool(options) {
|
|
|
2416
2478
|
onFound: own.onFound ?? o.onFound,
|
|
2417
2479
|
onAvailability: own.onAvailability ?? o.onAvailability,
|
|
2418
2480
|
onMerchantStatus: own.onMerchantStatus ?? o.onMerchantStatus,
|
|
2481
|
+
onStoreCheck: own.onStoreCheck ?? o.onStoreCheck,
|
|
2482
|
+
onRecheck: own.onRecheck ?? o.onRecheck,
|
|
2483
|
+
onFreshness: own.onFreshness ?? o.onFreshness,
|
|
2419
2484
|
onProducts: own.onProducts ?? o.onProducts,
|
|
2420
2485
|
// Agentcard's message takes its place in the turn's sequence, after the items before it, and goes to onText alone, so
|
|
2421
2486
|
// an app that passes onItem too hears it once.
|
|
@@ -0,0 +1,22 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* The shopper's size on a product card, as Agentcard sends it on a shoe or a garment it read at the store for the size the
|
|
3
|
+
* shopper named: the size asked for, the store's own label for it, and whether the store has it in stock. `available` is
|
|
4
|
+
* null when no read answered, and the card then says nothing of the size: an app never shows a size as in stock that
|
|
5
|
+
* Agentcard did not read. `line` is the words to show under the price, or null when there is nothing to say.
|
|
6
|
+
*/
|
|
7
|
+
export interface BuyCardSize {
|
|
8
|
+
/** The size the shopper asked for, as they read it: `men's 10.5`, `EU 44`, `size M`. */
|
|
9
|
+
requested: string;
|
|
10
|
+
/** The store's own label for that size (`10.5`, `M 10 / W 11.5`), or null when the store does not sell it. */
|
|
11
|
+
label: string | null;
|
|
12
|
+
/** True when the store has it in stock, false when it does not sell it or sold out of it, null when unread. */
|
|
13
|
+
available: boolean | null;
|
|
14
|
+
/** `men's 10.5 in stock` or `No men's 10.5 here`; null when `available` is null. */
|
|
15
|
+
line: string | null;
|
|
16
|
+
}
|
|
17
|
+
/** `shopper_size` as Agentcard sent it, or null when it sent none or a shape this version cannot read. */
|
|
18
|
+
export declare function cardSizeOf(v: unknown): BuyCardSize | null;
|
|
19
|
+
/** `{ shopper_size }` when Agentcard sent one, else nothing: a card with no size carries no field. */
|
|
20
|
+
export declare function cardSizeField(v: Record<string, unknown>): {
|
|
21
|
+
shopper_size?: BuyCardSize;
|
|
22
|
+
};
|
|
@@ -0,0 +1,23 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* The shopper's size on a product card, as Agentcard sends it on a shoe or a garment it read at the store for the size the
|
|
3
|
+
* shopper named: the size asked for, the store's own label for it, and whether the store has it in stock. `available` is
|
|
4
|
+
* null when no read answered, and the card then says nothing of the size: an app never shows a size as in stock that
|
|
5
|
+
* Agentcard did not read. `line` is the words to show under the price, or null when there is nothing to say.
|
|
6
|
+
*/
|
|
7
|
+
/** `shopper_size` as Agentcard sent it, or null when it sent none or a shape this version cannot read. */
|
|
8
|
+
export function cardSizeOf(v) {
|
|
9
|
+
if (!v || typeof v !== 'object')
|
|
10
|
+
return null;
|
|
11
|
+
const r = v;
|
|
12
|
+
if (typeof r.requested !== 'string' || !r.requested.trim())
|
|
13
|
+
return null;
|
|
14
|
+
const available = r.available === true ? true : r.available === false ? false : null;
|
|
15
|
+
const label = typeof r.label === 'string' && r.label.trim() ? r.label : null;
|
|
16
|
+
const line = available === true ? `${r.requested} in stock` : available === false ? `No ${r.requested} here` : null;
|
|
17
|
+
return { requested: r.requested, label, available, line };
|
|
18
|
+
}
|
|
19
|
+
/** `{ shopper_size }` when Agentcard sent one, else nothing: a card with no size carries no field. */
|
|
20
|
+
export function cardSizeField(v) {
|
|
21
|
+
const size = cardSizeOf(v.shopper_size);
|
|
22
|
+
return size ? { shopper_size: size } : {};
|
|
23
|
+
}
|
package/dist/client.d.ts
CHANGED
|
@@ -8,9 +8,11 @@
|
|
|
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';
|
|
15
|
+
import { type BuyCardSize } from './card-size.js';
|
|
14
16
|
import { type AgentcardListener, type ListenOptions } from './listen.js';
|
|
15
17
|
import { type ShopperMessage } from './shopper-message.js';
|
|
16
18
|
/** The fetch the client calls: Node 18 and later have one built in. */
|
|
@@ -93,6 +95,26 @@ export interface BuyAvailabilityUpdate {
|
|
|
93
95
|
availability: BuyAvailability;
|
|
94
96
|
}>;
|
|
95
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
|
+
}
|
|
96
118
|
/** How many products a running turn found so far, and how many of them it shows: "8 of 23 shown". */
|
|
97
119
|
export interface BuyFoundUpdate {
|
|
98
120
|
conversationId: string;
|
|
@@ -174,6 +196,52 @@ export interface BuyStreamHandlers {
|
|
|
174
196
|
* follow one store, keep the updates `isMerchantStatusAt(update, 'walmart.com')` accepts. The answer's `search` holds every store.
|
|
175
197
|
*/
|
|
176
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[];
|
|
177
245
|
}
|
|
178
246
|
export declare const DEFAULT_BASE_URL = "https://api.agentcard.sh";
|
|
179
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. */
|
|
@@ -429,6 +497,18 @@ export interface BuyFlightItinerary {
|
|
|
429
497
|
arrivalAt: string | null;
|
|
430
498
|
cabinClasses: Array<string | null>;
|
|
431
499
|
}>;
|
|
500
|
+
/** Who the price covers, when it covers more than one passenger: `priceCents` is then the whole party's. Absent for one passenger and from an older API. */
|
|
501
|
+
party?: BuyFlightParty;
|
|
502
|
+
}
|
|
503
|
+
/**
|
|
504
|
+
* A flight price for a party. `totalCents` is the price for every passenger together, the same as the card's `priceCents`.
|
|
505
|
+
* `perPassengerCents` is each passenger's share, adults first, when Agentcard knows it; absent when the fares differ by age.
|
|
506
|
+
*/
|
|
507
|
+
export interface BuyFlightParty {
|
|
508
|
+
adults: number;
|
|
509
|
+
children: number;
|
|
510
|
+
totalCents: number;
|
|
511
|
+
perPassengerCents?: number[];
|
|
432
512
|
}
|
|
433
513
|
export interface BuyCatalogItem {
|
|
434
514
|
flight?: BuyFlightItinerary;
|
|
@@ -452,8 +532,18 @@ export interface BuyCatalogItem {
|
|
|
452
532
|
url: string | null;
|
|
453
533
|
/** Whether a place has room at the asked time (BuyAvailability). Absent on a product with no such question. */
|
|
454
534
|
availability?: BuyAvailability;
|
|
535
|
+
/**
|
|
536
|
+
* Whether the product ships to the shopper's country: `yes` or `no` when the store lists the countries it ships to and
|
|
537
|
+
* Agentcard knows where the shopper is, `unknown` otherwise and from an older API. Agentcard never leaves a product out for
|
|
538
|
+
* it, and ranks a `no` behind the products that match the ask as well; an app may filter on it.
|
|
539
|
+
*/
|
|
540
|
+
ships_to_you: 'yes' | 'no' | 'unknown';
|
|
541
|
+
/** A short note for a product that ranks down for where it ships ("ships from the UK", "price in £"), or null. */
|
|
542
|
+
ships_note: string | null;
|
|
455
543
|
/** Who sells or books the product, where that is not the store the card names (`booked with OpenTable`); absent when Agentcard named none. */
|
|
456
544
|
fulfilled_by?: BuyFulfilledBy;
|
|
545
|
+
/** The size the shopper named, as the store's read of this product found it (BuyCardSize); absent when none was asked or read. */
|
|
546
|
+
shopper_size?: BuyCardSize;
|
|
457
547
|
}
|
|
458
548
|
/**
|
|
459
549
|
* The products the last search found. `presented` is true when this answer's reply presents them: the
|
|
@@ -664,6 +754,8 @@ export interface BuyConversation {
|
|
|
664
754
|
last_checkout: BuyLastCheckout | null;
|
|
665
755
|
orders: BuyOrder[];
|
|
666
756
|
carts: BuyCart[];
|
|
757
|
+
/** A trip being planned: each background search's status and results, flights first; null for any other conversation (trip.ts). */
|
|
758
|
+
trip: BuyTrip | null;
|
|
667
759
|
/**
|
|
668
760
|
* The reply Agentcard wrote since the shopper's last message, once no turn runs: the answer a call that ended with
|
|
669
761
|
* `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,12 +8,15 @@
|
|
|
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';
|
|
14
15
|
import { failedPartyOf, fulfilledByField } from './fulfilled-by.js';
|
|
16
|
+
import { cardSizeField } from './card-size.js';
|
|
15
17
|
import { listenToAgentcard } from './listen.js';
|
|
16
18
|
import { messageInChat } from './shopper-message.js';
|
|
19
|
+
const STORE_CHECK_STATES = new Set(['checking', 'confirmed', 'sold_out', 'unconfirmed']);
|
|
17
20
|
/**
|
|
18
21
|
* The words this SDK shows for a step whatever Agentcard's own line says, so every app reads the same line. `searching_wider`
|
|
19
22
|
* comes when a search across every store has shown no product for a while and keeps running; its products still come as cards.
|
|
@@ -22,7 +25,7 @@ export const STATUS_WORDING = Object.freeze({
|
|
|
22
25
|
searching_wider: 'Checking more stores for this…',
|
|
23
26
|
});
|
|
24
27
|
/** The handlers that ask Agentcard to stream a turn. */
|
|
25
|
-
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'];
|
|
26
29
|
const PROGRESS_STAGES = new Set(['searching', 'building_cart', 'checking_out', 'working']);
|
|
27
30
|
export const DEFAULT_BASE_URL = 'https://api.agentcard.sh';
|
|
28
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. */
|
|
@@ -107,6 +110,45 @@ export class AgentcardApiError extends Error {
|
|
|
107
110
|
}
|
|
108
111
|
const str = (v) => (typeof v === 'string' && v !== '' ? v : null);
|
|
109
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
|
+
}
|
|
110
152
|
const bool = (v) => (typeof v === 'boolean' ? v : null);
|
|
111
153
|
/**
|
|
112
154
|
* A product page's address: an absolute http or https URL, serialized the way
|
|
@@ -240,8 +282,18 @@ function flightOf(value) {
|
|
|
240
282
|
departureAt: str(leg.departureAt), arrivalAt: str(leg.arrivalAt),
|
|
241
283
|
cabinClasses: Array.isArray(leg.cabinClasses) ? leg.cabinClasses.map(str) : [] };
|
|
242
284
|
}),
|
|
285
|
+
...partyOf(value.party),
|
|
243
286
|
};
|
|
244
287
|
}
|
|
288
|
+
function partyOf(value) {
|
|
289
|
+
if (!isRecord(value))
|
|
290
|
+
return {};
|
|
291
|
+
const adults = num(value.adults), children = num(value.children), totalCents = num(value.totalCents);
|
|
292
|
+
if (adults === null || children === null || totalCents === null || adults + children < 2)
|
|
293
|
+
return {};
|
|
294
|
+
const each = Array.isArray(value.perPassengerCents) ? value.perPassengerCents.map(num) : null;
|
|
295
|
+
return { party: { adults, children, totalCents, ...(each && each.length === adults + children && each.every((c) => c !== null) ? { perPassengerCents: each } : {}) } };
|
|
296
|
+
}
|
|
245
297
|
function catalogOf(v) {
|
|
246
298
|
if (!isRecord(v))
|
|
247
299
|
return null;
|
|
@@ -265,7 +317,10 @@ function catalogItemsOf(v) {
|
|
|
265
317
|
store: storeOf(i.store),
|
|
266
318
|
url: pageUrl(i.url),
|
|
267
319
|
...availabilityField(i),
|
|
320
|
+
ships_to_you: i.ships_to_you === 'yes' || i.ships_to_you === 'no' ? i.ships_to_you : 'unknown',
|
|
321
|
+
ships_note: str(i.ships_note),
|
|
268
322
|
...fulfilledByField(i),
|
|
323
|
+
...cardSizeField(i),
|
|
269
324
|
};
|
|
270
325
|
}) : [];
|
|
271
326
|
}
|
|
@@ -404,6 +459,7 @@ function conversationOf(body) {
|
|
|
404
459
|
})) : [],
|
|
405
460
|
carts: cartsOf(body.carts),
|
|
406
461
|
latest_reply: latestReplyOf(body.latest_reply),
|
|
462
|
+
trip: tripOf(body.trip),
|
|
407
463
|
raw: body,
|
|
408
464
|
};
|
|
409
465
|
}
|
|
@@ -523,7 +579,7 @@ async function eachEvent(res, onEvent) {
|
|
|
523
579
|
}
|
|
524
580
|
/**
|
|
525
581
|
* A streamed purchase turn, read to its `envelope`: the same answer a JSON turn returns, with its
|
|
526
|
-
* status in `http_status`. Each `progress`, `status`, `delta`, `catalog`, `find`, `segment`, `reply_written`, `cart`, `venue_availability` and `
|
|
582
|
+
* status in `http_status`. Each `progress`, `status`, `delta`, `catalog`, `find`, `segment`, `reply_written`, `cart`, `venue_availability`, `merchant` and `store_check` goes to its handler
|
|
527
583
|
* on the way, and any other event, such as a `ping` or a `tool`, is read past. A stream that breaks or
|
|
528
584
|
* ends before its envelope, once it named the conversation, reads as a turn past its budget
|
|
529
585
|
* (`turn_timeout`), so the tool reads the conversation for what the turn left instead of losing it.
|
|
@@ -608,6 +664,14 @@ async function readStreamedTurn(res, stream, timeoutMs) {
|
|
|
608
664
|
else if (event === 'ack' && typeof data.text === 'string' && data.text.trim() !== '' && stream.onAck) {
|
|
609
665
|
deliver(() => stream.onAck({ conversationId: named, text: data.text }));
|
|
610
666
|
}
|
|
667
|
+
else if (event === 'recheck' && stream.onRecheck && (data.phase === 'started' || data.phase === 'finished')) {
|
|
668
|
+
const recheck = recheckOf(data, named);
|
|
669
|
+
deliver(() => stream.onRecheck(recheck));
|
|
670
|
+
}
|
|
671
|
+
else if (event === 'freshness' && stream.onFreshness && freshnessOf(data, named)) {
|
|
672
|
+
const one = freshnessOf(data, named);
|
|
673
|
+
deliver(() => stream.onFreshness(one));
|
|
674
|
+
}
|
|
611
675
|
else if (event === 'venue_availability' && stream.onAvailability && str(data.conversation_id) && Array.isArray(data.items)) {
|
|
612
676
|
const items = data.items.filter(isRecord).flatMap((i) => {
|
|
613
677
|
const id = str(i.id);
|
|
@@ -617,6 +681,18 @@ async function readStreamedTurn(res, stream, timeoutMs) {
|
|
|
617
681
|
if (items.length)
|
|
618
682
|
deliver(() => stream.onAvailability({ conversationId: data.conversation_id, items }));
|
|
619
683
|
}
|
|
684
|
+
else if (event === 'store_check' && stream.onStoreCheck && str(data.conversation_id) && isRecord(data.item)) {
|
|
685
|
+
const i = data.item;
|
|
686
|
+
const cardId = str(i.id);
|
|
687
|
+
const store = str(i.store);
|
|
688
|
+
if (cardId && store && typeof i.state === 'string' && STORE_CHECK_STATES.has(i.state)) {
|
|
689
|
+
const update = {
|
|
690
|
+
conversationId: data.conversation_id, cardId, store, storeName: str(i.store_name) ?? store,
|
|
691
|
+
state: i.state, priceCents: typeof i.price_cents === 'number' ? i.price_cents : null,
|
|
692
|
+
};
|
|
693
|
+
deliver(() => stream.onStoreCheck(update));
|
|
694
|
+
}
|
|
695
|
+
}
|
|
620
696
|
else if (event === 'merchant' && stream.onMerchantStatus) {
|
|
621
697
|
const update = merchantStatusOf(data);
|
|
622
698
|
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,13 +4,13 @@ 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 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';
|
|
11
11
|
export { instantAck, instantReaction, ackFor, keepsYes, ACK_REACTION, type AckForOptions } from './ack.js';
|
|
12
12
|
export { directCall, isProductAsk, type DirectCall, type DirectContext, type DirectReason } from './direct.js';
|
|
13
|
-
export { forModel, grandTotals, forModelError, stepAsWritten, withoutCardList, stripLinks, type BuyToolModelView, type BuyToolModelCart, type BuyToolGrandTotal, type BuyToolModelProduct, type BuyToolModelStore, type ShownCard, } from './model-view.js';
|
|
13
|
+
export { forModel, grandTotals, forModelError, stepAsWritten, withoutCardList, stripLinks, flightPartyWords, type BuyToolModelView, type BuyToolModelCart, type BuyToolGrandTotal, type BuyToolModelProduct, type BuyToolModelStore, type ShownCard, } from './model-view.js';
|
|
14
14
|
export { asBullets, choiceLines, replyAsWritten, textingStyle, withChoice, type ReplyAsWrittenOptions } from './texting.js';
|
|
15
15
|
export { appInstructions, APP_INSTRUCTIONS_VERSION, type AppInstructionsOptions } from './app-instructions.js';
|
|
16
16
|
export { toAnthropicTool, toOpenRouterTool, type AnthropicBuyTool, type OpenRouterToolkit, type ZodLike, } from './adapters.js';
|
|
@@ -21,4 +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';
|
|
26
|
+
export { cardSizeOf, type BuyCardSize } from './card-size.js';
|
package/dist/index.js
CHANGED
|
@@ -10,7 +10,7 @@ export { toMessages, messageStream, typingDelay, } from './messages.js';
|
|
|
10
10
|
export { buyTool, nextStepPrompt, productAction, } from './buy-tool.js';
|
|
11
11
|
export { instantAck, instantReaction, ackFor, keepsYes, ACK_REACTION } from './ack.js';
|
|
12
12
|
export { directCall, isProductAsk } from './direct.js';
|
|
13
|
-
export { forModel, grandTotals, forModelError, stepAsWritten, withoutCardList, stripLinks, } from './model-view.js';
|
|
13
|
+
export { forModel, grandTotals, forModelError, stepAsWritten, withoutCardList, stripLinks, flightPartyWords, } from './model-view.js';
|
|
14
14
|
export { asBullets, choiceLines, replyAsWritten, textingStyle, withChoice } from './texting.js';
|
|
15
15
|
export { appInstructions, APP_INSTRUCTIONS_VERSION } from './app-instructions.js';
|
|
16
16
|
export { toAnthropicTool, toOpenRouterTool, } from './adapters.js';
|
|
@@ -20,4 +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';
|
|
25
|
+
export { cardSizeOf } from './card-size.js';
|
package/dist/model-view.d.ts
CHANGED
|
@@ -7,7 +7,7 @@
|
|
|
7
7
|
* `product_links_sent` for the products' store pages, and `link_sent` on each
|
|
8
8
|
* store and `store_links_sent` for the pages of the stores to pick from.
|
|
9
9
|
*/
|
|
10
|
-
import type { BuyCart, BuyNextAction } from './client.js';
|
|
10
|
+
import type { BuyCart, BuyFlightParty, BuyNextAction } from './client.js';
|
|
11
11
|
import type { BuyToolProduct, BuyToolResult, BuyToolStatus, BuyToolStore } from './buy-tool.js';
|
|
12
12
|
export interface BuyToolModelCart {
|
|
13
13
|
store: string;
|
|
@@ -208,7 +208,15 @@ export declare function productPrice(product: {
|
|
|
208
208
|
priceCents: number | null;
|
|
209
209
|
currency: string | null;
|
|
210
210
|
priceFrom?: true;
|
|
211
|
+
flight?: {
|
|
212
|
+
party?: BuyFlightParty;
|
|
213
|
+
};
|
|
211
214
|
}): string | null;
|
|
215
|
+
/**
|
|
216
|
+
* Who a party's flight price covers, as every app shows it after the price: `for 2`, or `for 2 adults and 1 child` when
|
|
217
|
+
* children fly. `$1,610 for 2` is the price of both seats together.
|
|
218
|
+
*/
|
|
219
|
+
export declare function flightPartyWords(party: BuyFlightParty): string;
|
|
212
220
|
export declare function money(minor: number | null | undefined, currency: string | null | undefined): string | null;
|
|
213
221
|
/**
|
|
214
222
|
* The products the model reads: the first twenty that keep a name and a store
|
package/dist/model-view.js
CHANGED
|
@@ -110,8 +110,19 @@ function currencyFormat(currency) {
|
|
|
110
110
|
*/
|
|
111
111
|
export function productPrice(product) {
|
|
112
112
|
const price = money(product.priceCents, product.currency);
|
|
113
|
+
if (price !== null && product.flight?.party)
|
|
114
|
+
return `${price} ${flightPartyWords(product.flight.party)}`;
|
|
113
115
|
return price !== null && product.priceFrom ? `from ${price}` : price;
|
|
114
116
|
}
|
|
117
|
+
/**
|
|
118
|
+
* Who a party's flight price covers, as every app shows it after the price: `for 2`, or `for 2 adults and 1 child` when
|
|
119
|
+
* children fly. `$1,610 for 2` is the price of both seats together.
|
|
120
|
+
*/
|
|
121
|
+
export function flightPartyWords(party) {
|
|
122
|
+
if (party.children === 0)
|
|
123
|
+
return `for ${party.adults}`;
|
|
124
|
+
return `for ${party.adults} adult${party.adults === 1 ? '' : 's'} and ${party.children} ${party.children === 1 ? 'child' : 'children'}`;
|
|
125
|
+
}
|
|
115
126
|
export function money(minor, currency) {
|
|
116
127
|
if (typeof minor !== 'number' || !Number.isFinite(minor))
|
|
117
128
|
return null;
|
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.10",
|
|
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",
|