@agent-cards/commerce-sdk 0.13.0 → 0.14.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +44 -4
- package/dist/ack.d.ts +24 -0
- package/dist/ack.js +8 -8
- package/dist/buy-tool.d.ts +102 -13
- package/dist/buy-tool.js +274 -15
- package/dist/client.d.ts +104 -3
- package/dist/client.js +146 -17
- package/dist/definition.js +1 -0
- package/dist/direct.d.ts +50 -0
- package/dist/direct.js +237 -0
- package/dist/events.d.ts +1 -1
- package/dist/index.d.ts +4 -3
- package/dist/index.js +2 -1
- package/dist/model-view.d.ts +17 -0
- package/dist/model-view.js +127 -2
- package/dist/shopper-message.d.ts +2 -2
- package/package.json +9 -1
package/README.md
CHANGED
|
@@ -236,6 +236,28 @@ history = written !== null ? [...runner.params.messages, { role: 'assistant', co
|
|
|
236
236
|
|
|
237
237
|
Any other answer, such as an approval sent, an order placed or a refusal, leaves `turn.replyAsWritten` null, and your model words it. So does a line the tool writes itself for your model, such as the note that the cart changed or a `buy.nextStep` turn's ask for the shopper's yes: only a reply Agentcard sent goes out as written. `replyAsWritten(result)` applies the same rule to one answer, for a loop that reads `turn.results` itself. Leave the option off to have your model write every answer.
|
|
238
238
|
|
|
239
|
+
### Send a clear shopping message straight to Agentcard
|
|
240
|
+
|
|
241
|
+
Your model's first call on most messages only decides to call the buy tool. Pass `direct: true` to `buyTool`, and each turn reads its message by rule, with no model, and sets `turn.direct` when the message goes straight to Agentcard: a request to buy or book something (`find me usb-c cables under $20`, `aa batteries on amazon`), a pick of what Agentcard just showed (a card's tap, `the first one`, `1 and 3`), or the answer to the question Agentcard just asked (the address, a code, `medium`, or a plain yes to the total it asked the shopper to confirm). Send the call yourself, and with `replyAsWritten` the turn makes no model call at all:
|
|
242
|
+
|
|
243
|
+
```ts
|
|
244
|
+
const turn = buy.turn(message, { onMessage: (text) => sendText(shopper.id, text) });
|
|
245
|
+
let messages = turn.messages(history);
|
|
246
|
+
if (turn.direct) {
|
|
247
|
+
const id = `toolu_direct_${crypto.randomUUID().replace(/-/g, '')}`;
|
|
248
|
+
const content = await turn.run(turn.direct.input);
|
|
249
|
+
messages = [...messages, { role: 'assistant', content: [{ type: 'tool_use', id, name: turn.name, input: turn.direct.input }] }, { role: 'user', content: [{ type: 'tool_result', tool_use_id: id, content }] }];
|
|
250
|
+
if (turn.replyAsWritten !== null) {
|
|
251
|
+
await turn.say(turn.replyAsWritten);
|
|
252
|
+
history = [...messages, { role: 'assistant', content: turn.replyAsWritten }];
|
|
253
|
+
return;
|
|
254
|
+
}
|
|
255
|
+
}
|
|
256
|
+
// Otherwise run your model on `messages` as before: it words the answer to the call already made, or decides the call itself.
|
|
257
|
+
```
|
|
258
|
+
|
|
259
|
+
Everything else leaves `turn.direct` null for your model: a question, small talk, a change of mind such as `no` or `wait`, a command to the cart, and anything about the account or an order. A pick or an answer goes straight only when the shopper's last turn ended on Agentcard's answer, so a reply to something your model said reaches your model. `directCall(text, context)` applies the same rule to one message, for an app that keeps its own state. The option is off by default, because it moves the decision of when to call Agentcard from your model into the tool.
|
|
260
|
+
|
|
239
261
|
### Hand the app the turn in order
|
|
240
262
|
|
|
241
263
|
A turn reaches the shopper as messages and links that interleave: the instant reply, the cards a search found, then your model's words about them. Pass `onItem` to `buy.turn`, and the turn hands your app all of them as one sequence, one item at a time, in the order they happened. Show each item below the one before it:
|
|
@@ -254,7 +276,7 @@ for await (const stream of runner) {
|
|
|
254
276
|
}
|
|
255
277
|
```
|
|
256
278
|
|
|
257
|
-
- A `text` item is the instant reply, or a message your model wrote through `turn.messageStream()` or `turn.say(text)`. A `link` item is each link the tool hands over, with the fields `sendLink` would get.
|
|
279
|
+
- A `text` item is the instant reply, or a message your model wrote through `turn.messageStream()` or `turn.say(text)`. A `link` item is each link the tool hands over, with the fields `sendLink` would get. A message of Agentcard's that `onText` takes holds its place in `turn.items` as a `text` item too, and reaches `onText` alone.
|
|
258
280
|
- Each message goes out the moment it is ready. Pass `pace: true` to space them like someone texting: each one then waits until `typingDelay` of it has passed since the item before it went out, 300 ms plus 12 ms a character, at most 1.5 seconds. Time your model spent writing counts toward the pause, so a slow model gets no added wait, and links never wait. `onTyping` hears `true` when a pause starts and `false` when its message goes out. A function returns your own pause in milliseconds.
|
|
259
281
|
- With `onItem`, the turn sends no link to a `sendLink` and no instant reply to an `onWorking`, so a tool built with those for other turns sends nothing twice. Passing `onItem` beside `sendLink` or `onWorking` to the same turn throws.
|
|
260
282
|
- `turn.items` holds every item that went out, in order, so an app that answers the whole turn at once returns it as is. A turn without `onItem` keeps `turn.items` as well, unpaced.
|
|
@@ -418,18 +440,35 @@ Without a buy tool, `instantReaction(message, { nextAction, asked, stores })` an
|
|
|
418
440
|
|
|
419
441
|
## Show what Agentcard is doing
|
|
420
442
|
|
|
421
|
-
|
|
443
|
+
The tool streams every call, so each part of an answer reaches your app the moment Agentcard produces it: the stage the call reached, the products a search found, each message Agentcard writes and the cart it builds. Pass a callback for each part you show:
|
|
422
444
|
|
|
423
445
|
```ts
|
|
424
446
|
const buy = buyTool({
|
|
425
447
|
agentcard,
|
|
426
448
|
externalUserId: shopper.id,
|
|
427
449
|
sendLink: (link) => textShopper(shopper.id, link.url),
|
|
428
|
-
|
|
450
|
+
onStatus: ({ text }) => showStatus(shopper.id, text),
|
|
451
|
+
onFound: ({ shown, found, more }) => showCount(shopper.id, `${shown} of ${found} shown`, more),
|
|
452
|
+
onProducts: (products, { merchantName }) => showProducts(shopper.id, merchantName, products),
|
|
453
|
+
onText: (text) => textShopper(shopper.id, text),
|
|
454
|
+
onCart: (cart, { carts }) => showCart(shopper.id, cart, carts),
|
|
429
455
|
});
|
|
430
456
|
```
|
|
431
457
|
|
|
432
|
-
|
|
458
|
+
- `onStatus` hears each step the moment Agentcard starts it, as a line to show in place of loading dots: `searching Target and Walmart`, `adding it to your cart`, `working through the store's checkout`. `step` names the step, such as `searching` or `adding_to_cart`, for an app that writes its own words. A call that only writes its reply sends none, so keep your dots until the first line comes.
|
|
459
|
+
- `onFound` hears how many products the searches found, after the cards it counts: `shown` cards of `found` products, such as "8 of 23 shown", each time the count grows. `more` holds the products past the cards as product links, in the order they were found, for a "show more" that runs no new search. The model never reads them.
|
|
460
|
+
- `onProgress` still hears the stage, `searching`, `building_cart`, `checking_out` or `working`.
|
|
461
|
+
- `onProducts` hears the products a search found the moment it answers, before Agentcard writes its reply, with the conversation, when the search ran (`asOf`) and the store's name. With `sendLink`, each product's card goes out then too, a few seconds before the answer, and the answer sends only the cards it adds. Pass `earlyCards: false` to send only the cards the answer names, with the answer: the reply often names only some of a search's products.
|
|
462
|
+
- `onText` hears each message Agentcard writes, links included, the moment it is written, and after the cards of the search it is about.
|
|
463
|
+
- `onCart` hears the cart a confirm would place, or null, and every cart on the table, each time a call builds or changes one, before the message that announces it.
|
|
464
|
+
|
|
465
|
+
`buy.turn` takes the same callbacks, and each one left out falls back to the tool's. A turn's `onText` hears each message in its place in the turn, after the instant reply and the cards, paced by `pace`, and `turn.items` lists it; `onItem` does not hear it, so an app that passes both hears each message once.
|
|
466
|
+
|
|
467
|
+
When your app shows Agentcard's messages through `onText`, the model reads `shopper_already_read_reply: true` in that call's answer and adds nothing to them, as it reads `shopper_already_told_looking` after an instant reply: end the turn without a second model call, as with `replyAsWritten`, or let the model add one short line for what the reply left out. `turn.replyAsWritten` stays null for that answer, so the reply never reaches the shopper twice, and a step link the turn holds for your model's next message goes out right after Agentcard's ask instead.
|
|
468
|
+
|
|
469
|
+
A stream that breaks after the call started reads the conversation for what the turn left, as a turn past Agentcard's budget does. Pass `stream: false` to wait for each whole answer, with its cards sent beside it.
|
|
470
|
+
|
|
471
|
+
Code that calls the client itself streams with `agentcard.buy(body, handlers)`: `onCatalog`, `onSegment`, `onCart` and `onDelta` hear their events in the order Agentcard sent them, each once the one before it settled, and the answer waits for the last of them. `onDelta` asks Agentcard for the pieces of each message as it writes them, a draft that each `onSegment` message replaces.
|
|
433
472
|
|
|
434
473
|
## Send each paragraph as its own message
|
|
435
474
|
|
|
@@ -492,6 +531,7 @@ The model reads one object per call:
|
|
|
492
531
|
"approval_expires_at": null,
|
|
493
532
|
"shopper_need_not_reply": false,
|
|
494
533
|
"shopper_already_told_looking": false,
|
|
534
|
+
"shopper_already_read_reply": false,
|
|
495
535
|
"link_sent": false,
|
|
496
536
|
"product_links_sent": 0,
|
|
497
537
|
"pick": null,
|
package/dist/ack.d.ts
CHANGED
|
@@ -14,6 +14,28 @@
|
|
|
14
14
|
* instant reply at all, because it starts no search and Agentcard answers it fast. So does a pick of a
|
|
15
15
|
* store the shopper saw, such as `order here`, or `Order from Tacos El Grullense` as a store card sends it.
|
|
16
16
|
*/
|
|
17
|
+
/** Words a reply opens with before it says what it means, such as `ok` in `ok lets checkout` or `yes,` in `yes, sounds good`. */
|
|
18
|
+
export declare const FILLER: RegExp;
|
|
19
|
+
/** A bare acknowledgement, such as `its ok`, `thats fine`, `sounds good` or `works for me`, after the filler comes off. */
|
|
20
|
+
export declare const ACKNOWLEDGED: RegExp;
|
|
21
|
+
/** A step toward paying for the cart already on the table, such as `lets checkout`, `place the order` or `pay`, after the filler comes off. */
|
|
22
|
+
export declare const CHECKOUT: RegExp;
|
|
23
|
+
/**
|
|
24
|
+
* A question about the conversation, an order or how things work, such as `how did it know to msg that
|
|
25
|
+
* number?`, `is it booked?` or `where is my order`: the model answers it straight away, with no search.
|
|
26
|
+
*/
|
|
27
|
+
export declare const ABOUT: RegExp;
|
|
28
|
+
/** A command to the cart or the order, such as `lets clear the cart`, `remove the taco` or `change the quantity to 2`. */
|
|
29
|
+
export declare const CART_COMMAND: RegExp;
|
|
30
|
+
/** Small talk that asks for nothing to be bought, after `please` and `thanks` come off. */
|
|
31
|
+
export declare const CHITCHAT: RegExp;
|
|
32
|
+
/**
|
|
33
|
+
* A question about what the agent can buy or where it shops, such as `what stores can you buy from`,
|
|
34
|
+
* `where can you shop` or `which stores do you support`: the model answers it in a line or two, with no
|
|
35
|
+
* search, so no instant reply says it is looking. A question that names a product, such as `which stores
|
|
36
|
+
* can you buy aa batteries from`, is a search and keeps its reply.
|
|
37
|
+
*/
|
|
38
|
+
export declare const CAPABILITY: RegExp;
|
|
17
39
|
/**
|
|
18
40
|
* True when the message names one of these stores and nothing else to buy: the store's whole name, or
|
|
19
41
|
* two words of it in a row, such as `el grullense` for `Tacos El Grullense`, alone or after a frame such
|
|
@@ -23,6 +45,8 @@
|
|
|
23
45
|
export declare function namesStore(text: string, storeNames: readonly string[] | null | undefined): boolean;
|
|
24
46
|
/** True when the shopper's words pick a product they already saw, such as `the first one`, `2` or a card's title with its price. */
|
|
25
47
|
export declare function isPickText(text: string): boolean;
|
|
48
|
+
/** The text as one plain line: lowercase, no quotes, and no dash between words. */
|
|
49
|
+
export declare function plain(text: string): string;
|
|
26
50
|
/**
|
|
27
51
|
* True when the shopper's words start a request, and false for an answer to a question, a pick, a
|
|
28
52
|
* code, small talk, or a message that carries the shopper's own details, such as a phone number or a
|
package/dist/ack.js
CHANGED
|
@@ -31,11 +31,11 @@ const TEMPLATES = [
|
|
|
31
31
|
*/
|
|
32
32
|
const WAITS = ['one moment', 'just a sec', 'working on it', 'on it, one sec', 'hang tight', 'one sec, working on it'];
|
|
33
33
|
/** Words a reply opens with before it says what it means, such as `ok` in `ok lets checkout` or `yes,` in `yes, sounds good`. */
|
|
34
|
-
const FILLER = /^(?:(?:ok|okay|k|kk|yes|yeah|yea|yep|yup|ya|sure|alright|all right|cool|great|perfect|nice|so|then|and|um|uh|oh|well|hmm|now)[\s,]+)+/;
|
|
34
|
+
export const FILLER = /^(?:(?:ok|okay|k|kk|yes|yeah|yea|yep|yup|ya|sure|alright|all right|cool|great|perfect|nice|so|then|and|um|uh|oh|well|hmm|now)[\s,]+)+/;
|
|
35
35
|
/** A bare acknowledgement, such as `its ok`, `thats fine`, `sounds good` or `works for me`, after the filler comes off. */
|
|
36
|
-
const ACKNOWLEDGED = /^(?:ok|okay|k|kk|fine|alright|all right|cool|great|perfect|good|nice|sure|yes|yeah|yep|yup|right|correct|exactly|agreed|works|(?:that|this|it) works|(?:it|that|this)(?:'?s| is) (?:ok|okay|fine|good|great|perfect|cool|right|correct|alright|all right|all good|it)|its (?:ok|okay|fine|good|great|perfect|cool|right|correct|alright|all good)|thats (?:ok|okay|fine|good|great|perfect|cool|right|correct|alright|it|all)|sounds (?:good|great|fine|perfect|right|ok|okay)|looks (?:good|great|fine|perfect|right|ok|okay)|all good|all set|no worries|no problem|np|got it|gotcha|understood|makes sense|(?:works|fine|ok|okay|good) (?:for|by|with) me|i'?m good|im good|we'?re good)(?: (?:then|now|with me|for me|to me))?$/;
|
|
36
|
+
export const ACKNOWLEDGED = /^(?:ok|okay|k|kk|fine|alright|all right|cool|great|perfect|good|nice|sure|yes|yeah|yep|yup|right|correct|exactly|agreed|works|(?:that|this|it) works|(?:it|that|this)(?:'?s| is) (?:ok|okay|fine|good|great|perfect|cool|right|correct|alright|all right|all good|it)|its (?:ok|okay|fine|good|great|perfect|cool|right|correct|alright|all good)|thats (?:ok|okay|fine|good|great|perfect|cool|right|correct|alright|it|all)|sounds (?:good|great|fine|perfect|right|ok|okay)|looks (?:good|great|fine|perfect|right|ok|okay)|all good|all set|no worries|no problem|np|got it|gotcha|understood|makes sense|(?:works|fine|ok|okay|good) (?:for|by|with) me|i'?m good|im good|we'?re good)(?: (?:then|now|with me|for me|to me))?$/;
|
|
37
37
|
/** A step toward paying for the cart already on the table, such as `lets checkout`, `place the order` or `pay`, after the filler comes off. */
|
|
38
|
-
const CHECKOUT = /^(?:(?:let'?s|lets|i want to|we want to|i'?d like to|id like to|i'?m ready to|im ready to|ready to|can you|could you|can we|go ahead and|just|please)\s+)*(?:check ?out|pay|pay now|pay for (?:it|this|that|them|the order|my order|everything)|place (?:the |my |this |that |our )?order|place it|confirm(?: (?:the|my) (?:order|purchase|cart)| it)?|complete (?:the |my )?(?:order|purchase|checkout)|finish (?:the |my )?(?:order|purchase|checkout)|proceed(?: to (?:checkout|payment|pay))?|go ahead|go for it|buy (?:it|them|this|that|these|those)(?: now)?|order it|book it|submit (?:the |my )?order|send (?:the |my )?order|do it|go|ready|i'?m ready|im ready)(?: now| then| already)?$/;
|
|
38
|
+
export const CHECKOUT = /^(?:(?:let'?s|lets|i want to|we want to|i'?d like to|id like to|i'?m ready to|im ready to|ready to|can you|could you|can we|go ahead and|just|please)\s+)*(?:check ?out|pay|pay now|pay for (?:it|this|that|them|the order|my order|everything)|place (?:the |my |this |that |our )?order|place it|confirm(?: (?:the|my) (?:order|purchase|cart)| it)?|complete (?:the |my )?(?:order|purchase|checkout)|finish (?:the |my )?(?:order|purchase|checkout)|proceed(?: to (?:checkout|payment|pay))?|go ahead|go for it|buy (?:it|them|this|that|these|those)(?: now)?|order it|book it|submit (?:the |my )?order|send (?:the |my )?order|do it|go|ready|i'?m ready|im ready)(?: now| then| already)?$/;
|
|
39
39
|
/** Whole replies that answer a question the shopper was asked, after `please` and `thanks` come off. */
|
|
40
40
|
const ANSWERS = new RegExp('^(?:' +
|
|
41
41
|
[
|
|
@@ -53,9 +53,9 @@ const STATUS = /^(?:(?:ok|okay|yes|yep|yeah|alright|cool|great|so|done|there)[\s
|
|
|
53
53
|
* A question about the conversation, an order or how things work, such as `how did it know to msg that
|
|
54
54
|
* number?`, `is it booked?` or `where is my order`: the model answers it straight away, with no search.
|
|
55
55
|
*/
|
|
56
|
-
const ABOUT = /^(?:(?:ok|okay|so|wait|hmm|um|uh|and|but)[\s,]+)*(?:how (?:did|does|do|come|is|was|were|are)|why (?:did|does|do|is|was|are|were|would|didn'?t|isn'?t|won'?t)|what(?:'s| is| was) (?:my|the status|happening|going on|that|this)|what happened|where(?:'s| is| are) (?:my|the order|it)|(?:is|was|has|did|will) (?:it|my|this|that|the order|the booking|the reservation)|did (?:it|that|you) (?:go|work|book|place|send|get|cancel|charge|order|change|add|remove)|will you (?:let|tell|text|notify|email|update|remind|ping|keep (?:me|us) (?:updated|posted|informed|in the loop))|when (?:will|does|do|is|did) (?:it|my|the order|they)|how long (?:will|does|until|till))\b/;
|
|
56
|
+
export const ABOUT = /^(?:(?:ok|okay|so|wait|hmm|um|uh|and|but)[\s,]+)*(?:how (?:did|does|do|come|is|was|were|are)|why (?:did|does|do|is|was|are|were|would|didn'?t|isn'?t|won'?t)|what(?:'s| is| was) (?:my|the status|happening|going on|that|this)|what happened|where(?:'s| is| are) (?:my|the order|it)|(?:is|was|has|did|will) (?:it|my|this|that|the order|the booking|the reservation)|did (?:it|that|you) (?:go|work|book|place|send|get|cancel|charge|order|change|add|remove)|will you (?:let|tell|text|notify|email|update|remind|ping|keep (?:me|us) (?:updated|posted|informed|in the loop))|when (?:will|does|do|is|did) (?:it|my|the order|they)|how long (?:will|does|until|till))\b/;
|
|
57
57
|
/** A command to the cart or the order, such as `lets clear the cart`, `remove the taco` or `change the quantity to 2`. */
|
|
58
|
-
const CART_COMMAND = new RegExp('^(?:(?:ok|okay|so|actually|wait|and|um|uh)[\\s,]+)*(?:let\'?s |lets |can you |could you |pls |i want to |i\'?d like to |id like to |i need to |go ahead and |just )*(?:' +
|
|
58
|
+
export const CART_COMMAND = new RegExp('^(?:(?:ok|okay|so|actually|wait|and|um|uh)[\\s,]+)*(?:let\'?s |lets |can you |could you |pls |i want to |i\'?d like to |id like to |i need to |go ahead and |just )*(?:' +
|
|
59
59
|
[
|
|
60
60
|
// Verbs that never open a search: `remove fries`, `cancel order`.
|
|
61
61
|
'remove|delete|cancel|undo|start over|take off|get rid of',
|
|
@@ -66,14 +66,14 @@ const CART_COMMAND = new RegExp('^(?:(?:ok|okay|so|actually|wait|and|um|uh)[\\s,
|
|
|
66
66
|
].join('|') +
|
|
67
67
|
')\\b');
|
|
68
68
|
/** Small talk that asks for nothing to be bought, after `please` and `thanks` come off. */
|
|
69
|
-
const CHITCHAT = /^(?:(?:hi|hey|hello|yo|hiya|howdy|sup)(?: there| again)?|good (?:morning|afternoon|evening|night)|g(?:oo)?d ?night|how are (?:you|u)(?: doing)?(?: today)?|how'?s it going|what'?s up|whats up|wassup|who are (?:you|u)|what (?:can|do) (?:you|u) do|are (?:you|u) (?:a )?(?:bot|real|human|there)|you there|test(?:ing)?|bye|see (?:you|ya)|later|love (?:it|you|u)|awesome|amazing|wow|omg|ha(?:ha)+|lmao|:\)|ok cool|cool thanks|so much|very much|a lot)$/;
|
|
69
|
+
export const CHITCHAT = /^(?:(?:hi|hey|hello|yo|hiya|howdy|sup)(?: there| again)?|good (?:morning|afternoon|evening|night)|g(?:oo)?d ?night|how are (?:you|u)(?: doing)?(?: today)?|how'?s it going|what'?s up|whats up|wassup|who are (?:you|u)|what (?:can|do) (?:you|u) do|are (?:you|u) (?:a )?(?:bot|real|human|there)|you there|test(?:ing)?|bye|see (?:you|ya)|later|love (?:it|you|u)|awesome|amazing|wow|omg|ha(?:ha)+|lmao|:\)|ok cool|cool thanks|so much|very much|a lot)$/;
|
|
70
70
|
/**
|
|
71
71
|
* A question about what the agent can buy or where it shops, such as `what stores can you buy from`,
|
|
72
72
|
* `where can you shop` or `which stores do you support`: the model answers it in a line or two, with no
|
|
73
73
|
* search, so no instant reply says it is looking. A question that names a product, such as `which stores
|
|
74
74
|
* can you buy aa batteries from`, is a search and keeps its reply.
|
|
75
75
|
*/
|
|
76
|
-
const CAPABILITY = /^(?:(?:ok|okay|so|and|but|hey|hi|um|uh|also|wait)[\s,]+)*(?:(?:what|which|where)(?: (?:kinds?|types?|sorts?) of)?(?: (?:other|else|all|the))?(?: (?:stores?|shops?|places?|sites?|websites?|merchants?|retailers?|restaurants?|brands?|products?|items?|things?|stuff))?(?: else)? (?:can|do|could|will|are) (?:you|u)(?: (?:able to|even|actually|also))? (?:buy|shop|order|get|purchase|do|use|support|work with|deliver|cover|access|sell|carry|offer|stock)(?: (?:from|at|on|in|with|for me|for us|to))?(?: for me)?|(?:what|which) (?:stores?|shops?|merchants?|retailers?|sites?) (?:are )?(?:supported|available|connected|on here)|(?:what|which) (?:stores?|shops?|merchants?|retailers?) do (?:you|u) (?:have|know|use|support|work with)|what (?:else )?(?:do|can) (?:you|u) (?:offer|sell|carry|get (?:me|us)|buy (?:me|us)|order (?:me|us)|find (?:me|us)))(?: (?:right now|here|today))?$/;
|
|
76
|
+
export const CAPABILITY = /^(?:(?:ok|okay|so|and|but|hey|hi|um|uh|also|wait)[\s,]+)*(?:(?:what|which|where)(?: (?:kinds?|types?|sorts?) of)?(?: (?:other|else|all|the))?(?: (?:stores?|shops?|places?|sites?|websites?|merchants?|retailers?|restaurants?|brands?|products?|items?|things?|stuff))?(?: else)? (?:can|do|could|will|are) (?:you|u)(?: (?:able to|even|actually|also))? (?:buy|shop|order|get|purchase|do|use|support|work with|deliver|cover|access|sell|carry|offer|stock)(?: (?:from|at|on|in|with|for me|for us|to))?(?: for me)?|(?:what|which) (?:stores?|shops?|merchants?|retailers?|sites?) (?:are )?(?:supported|available|connected|on here)|(?:what|which) (?:stores?|shops?|merchants?|retailers?) do (?:you|u) (?:have|know|use|support|work with)|what (?:else )?(?:do|can) (?:you|u) (?:offer|sell|carry|get (?:me|us)|buy (?:me|us)|order (?:me|us)|find (?:me|us)))(?: (?:right now|here|today))?$/;
|
|
77
77
|
/** Unit words a number names a product by, such as `10000 mah`, so it is never read as a zip code. */
|
|
78
78
|
const UNIT = '(?:packs?|count|ct|pcs?|pieces?|oz|ml|l|g|kg|gb|tb|mb|mah|w|watts?|v|volts?|lumens?|lm|inch(?:es)?|in|ft|feet|mm|cm|lbs?|dollars?|bucks|rpm|dpi|btu|cal|calories|thread)';
|
|
79
79
|
/**
|
|
@@ -226,7 +226,7 @@ const LEADING_PREPOSITION = /^(?:from|at|in|on|to|for|of|with|by|near|into|onto|
|
|
|
226
226
|
const MAX_WORDS = 8;
|
|
227
227
|
const MAX_WORD_LENGTH = 24;
|
|
228
228
|
/** The text as one plain line: lowercase, no quotes, and no dash between words. */
|
|
229
|
-
function plain(text) {
|
|
229
|
+
export function plain(text) {
|
|
230
230
|
return text.toLowerCase().replace(/[‘’]/g, "'").replace(/["“”`]/g, '').replace(/\s+[-–—]+\s+|[–—]/g, ' ').replace(/\s+/g, ' ').trim();
|
|
231
231
|
}
|
|
232
232
|
/**
|
package/dist/buy-tool.d.ts
CHANGED
|
@@ -31,8 +31,9 @@
|
|
|
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 { AgentcardClient, type BuyCart, type BuyCartBuilding, type BuyFlightItinerary, type BuyDeliveryAddress, type BuyNextAction, type BuyPick, type BuyProgress, type BuyStorePick, type BuyTurn, type FetchLike } from './client.js';
|
|
34
|
+
import { AgentcardClient, type BuyCart, type BuyCartBuilding, type BuyFlightItinerary, type BuyDeliveryAddress, type BuyNextAction, type BuyPick, type BuyProgress, type BuyTurnStatus, type BuyStorePick, type BuyTurn, type FetchLike } from './client.js';
|
|
35
35
|
import { type BuyToolDefinition, type BuyToolInput, type BuyToolInputSchema, type BuyToolStandardSchema, type OpenAIBuyTool } from './definition.js';
|
|
36
|
+
import { type DirectCall } from './direct.js';
|
|
36
37
|
import { type BuyToolModelView } from './model-view.js';
|
|
37
38
|
import { type MessagePace, type MessageStream } from './messages.js';
|
|
38
39
|
import { type ShopperMessage } from './shopper-message.js';
|
|
@@ -188,6 +189,11 @@ export interface BuyToolResult {
|
|
|
188
189
|
* the request is being worked on. Absent or false when none went out.
|
|
189
190
|
*/
|
|
190
191
|
shopper_already_told_looking?: boolean;
|
|
192
|
+
/**
|
|
193
|
+
* True when every message of Agentcard's answer reached the shopper through `onText` as it was written, so the shopper
|
|
194
|
+
* already read `reply`. Absent or false when any message did not go out that way, and when the tool wrote the reply.
|
|
195
|
+
*/
|
|
196
|
+
shopper_already_read_reply?: boolean;
|
|
191
197
|
conversation_id: string | null;
|
|
192
198
|
order_id: string | null;
|
|
193
199
|
decline_code: string | null;
|
|
@@ -218,6 +224,18 @@ export interface BuyToolResult {
|
|
|
218
224
|
* - `none`: nothing.
|
|
219
225
|
*/
|
|
220
226
|
export type BuyAckMode = 'reaction' | 'text' | 'none';
|
|
227
|
+
/**
|
|
228
|
+
* How many products a turn's searches found, sent each time the count grows, after the cards it counts: `shown` cards of
|
|
229
|
+
* `found` products, "8 of 23 shown". `more` holds the products found past the cards, in the order they were found, as
|
|
230
|
+
* product links the app reveals on "show more" with no new search; the model never reads them. Find mode sends a count
|
|
231
|
+
* with no `more`.
|
|
232
|
+
*/
|
|
233
|
+
export interface BuyToolFound {
|
|
234
|
+
conversationId: string;
|
|
235
|
+
shown: number;
|
|
236
|
+
found: number;
|
|
237
|
+
more: BuyToolLink[];
|
|
238
|
+
}
|
|
221
239
|
export interface BuyToolOptions {
|
|
222
240
|
/** A client built with your company's credentials. Build it once and share it across every shopper's tool. */
|
|
223
241
|
agentcard?: AgentcardClient;
|
|
@@ -288,17 +306,58 @@ export interface BuyToolOptions {
|
|
|
288
306
|
*/
|
|
289
307
|
onProgress?: (progress: BuyProgress) => void;
|
|
290
308
|
/**
|
|
291
|
-
*
|
|
292
|
-
*
|
|
309
|
+
* Called the moment Agentcard starts a step of a call, with a short line to show in place of loading dots, such as
|
|
310
|
+
* `searching Target and Walmart` or `adding it to your cart`. No status comes for a call that only writes its reply, so
|
|
311
|
+
* keep the dots until the first one. One that throws costs nothing but the call to it.
|
|
312
|
+
*/
|
|
313
|
+
onStatus?: (status: BuyTurnStatus) => void;
|
|
314
|
+
/**
|
|
315
|
+
* Hears how many products a call's searches found, each time the count grows (`BuyToolFound`), right after the cards it
|
|
316
|
+
* counts went out through `sendLink`. It needs `earlyCards`, the default: with the cards held for the answer, no count
|
|
317
|
+
* comes. One that throws costs nothing but the call to it.
|
|
318
|
+
*/
|
|
319
|
+
onFound?: (found: BuyToolFound) => void | Promise<void>;
|
|
320
|
+
/**
|
|
321
|
+
* Hears the products a call's search found, the moment the search answers and before Agentcard writes its reply,
|
|
322
|
+
* with the conversation, when the search ran (`asOf`, as the answer's `catalog.as_of` names it) and the store's name.
|
|
323
|
+
* Show them as the answer's cards. Like `sendLink`, it hears every call; pass it to `buy.turn` to hear only the calls
|
|
324
|
+
* of one turn. One that throws costs nothing but the call to it.
|
|
325
|
+
*/
|
|
326
|
+
onProducts?: (products: BuyToolProduct[], search: {
|
|
327
|
+
conversationId: string;
|
|
328
|
+
asOf: string | null;
|
|
329
|
+
merchantName: string;
|
|
330
|
+
}) => void | Promise<void>;
|
|
331
|
+
/**
|
|
332
|
+
* Hears each message Agentcard writes for the shopper, the moment it is written, links included, as `result.reply`
|
|
333
|
+
* and `result.messages` hold them. A message that lands before the instant reply was due quiets it, and one after it
|
|
334
|
+
* follows it. When every message of an answer went out through it, the model reads `shopper_already_read_reply` and
|
|
335
|
+
* adds nothing to them. One that throws leaves that flag false, so the model words the answer.
|
|
336
|
+
*/
|
|
337
|
+
onText?: (text: string, info: {
|
|
338
|
+
conversationId: string | null;
|
|
339
|
+
}) => void | Promise<void>;
|
|
340
|
+
/**
|
|
341
|
+
* Hears the carts each time a call builds or changes one, before the message that announces it: the cart a confirm
|
|
342
|
+
* would place, or null, and every cart on the table. One that throws costs nothing but the call to it.
|
|
343
|
+
*/
|
|
344
|
+
onCart?: (cart: BuyCart | null, info: {
|
|
345
|
+
carts: BuyCart[];
|
|
346
|
+
conversationId: string;
|
|
347
|
+
}) => void | Promise<void>;
|
|
348
|
+
/**
|
|
349
|
+
* `false` waits for each whole answer, and a search's cards go out with the answer that names them. Left out, the
|
|
350
|
+
* tool streams each call: the stages reach `onProgress`, the products `onProducts` and their cards `sendLink` the
|
|
351
|
+
* moment the search answers, each message `onText`, and the carts `onCart`, as Agentcard produces them. A call with
|
|
352
|
+
* none of these to hear it asks for the whole answer.
|
|
293
353
|
*/
|
|
294
354
|
stream?: false;
|
|
295
355
|
/**
|
|
296
|
-
* `true
|
|
297
|
-
* answers, before Agentcard writes its reply, and the answer then sends only the pages it adds.
|
|
298
|
-
*
|
|
299
|
-
*
|
|
300
|
-
*
|
|
301
|
-
* outside the first three.
|
|
356
|
+
* Default `true`: the store page of every product a search found, at most eight, goes to `sendLink` the moment the
|
|
357
|
+
* search answers, before Agentcard writes its reply, and the answer then sends only the pages it adds. Measured on
|
|
358
|
+
* five live searches on 2026-10-20, the early pages arrived 2.2 to 3.3 seconds before the answer. `false` waits for
|
|
359
|
+
* the answer and sends only the cards it names: the reply often names only some of the search's products, so early
|
|
360
|
+
* cards can include ones the reply never names.
|
|
302
361
|
*/
|
|
303
362
|
earlyCards?: boolean;
|
|
304
363
|
/**
|
|
@@ -308,6 +367,14 @@ export interface BuyToolOptions {
|
|
|
308
367
|
* writes every answer.
|
|
309
368
|
*/
|
|
310
369
|
replyAsWritten?: boolean;
|
|
370
|
+
/**
|
|
371
|
+
* `true` reads each shopper message `buy.turn` opens by rule, with no model, and sets `turn.direct` to the call
|
|
372
|
+
* it makes straight to Agentcard when the message is a request to buy, a pick of what Agentcard just showed, or
|
|
373
|
+
* the answer to the question Agentcard just asked (`directCall`). Send it with `turn.run(turn.direct.input)` and
|
|
374
|
+
* skip your model's first call; every other message, small talk and questions among them, leaves `turn.direct`
|
|
375
|
+
* null for your model to decide. Off by default, so your model decides every call.
|
|
376
|
+
*/
|
|
377
|
+
direct?: boolean;
|
|
311
378
|
}
|
|
312
379
|
/**
|
|
313
380
|
* The buy tool, in the shape every agent framework takes, because their field
|
|
@@ -427,7 +494,7 @@ export interface BuyToolTurnOptions {
|
|
|
427
494
|
*/
|
|
428
495
|
onItem?: (item: BuyToolTurnItem) => void | Promise<void>;
|
|
429
496
|
/**
|
|
430
|
-
* With `onItem` or `
|
|
497
|
+
* With `onItem`, `onMessage` or `onText`: how the turn's messages are spaced. Default `false`: each message goes
|
|
431
498
|
* out the moment it is ready. `true` spaces them like someone texting: a message waits until
|
|
432
499
|
* `typingDelay` of it has passed since the item before it went out, and time the model spent writing
|
|
433
500
|
* counts toward the pause. A function returns the pause in milliseconds for a message. Links never wait.
|
|
@@ -438,7 +505,7 @@ export interface BuyToolTurnOptions {
|
|
|
438
505
|
* null, so your model rewords every answer. Default: the tool's `replyAsWritten`.
|
|
439
506
|
*/
|
|
440
507
|
replyAsWritten?: boolean;
|
|
441
|
-
/** With `onItem` or `
|
|
508
|
+
/** With `onItem`, `onMessage` or `onText`: hears `true` when the pause before a message starts and `false` the moment that message goes out, for a typing indicator. */
|
|
442
509
|
onTyping?: (typing: boolean) => void;
|
|
443
510
|
/**
|
|
444
511
|
* Without `onItem`: hears each message your model writes through `turn.messageStream()` or
|
|
@@ -461,6 +528,20 @@ export interface BuyToolTurnOptions {
|
|
|
461
528
|
onResult?: (result: BuyToolResult) => void | Promise<void>;
|
|
462
529
|
/** Called with each stage Agentcard reaches on a call this turn makes. */
|
|
463
530
|
onProgress?: (progress: BuyProgress) => void;
|
|
531
|
+
/** Called with each step Agentcard starts on a call this turn makes, as the tool's `onStatus` is. */
|
|
532
|
+
onStatus?: BuyToolOptions['onStatus'];
|
|
533
|
+
/** Hears how many products this turn's calls found, as the tool's `onFound` does. */
|
|
534
|
+
onFound?: BuyToolOptions['onFound'];
|
|
535
|
+
/** Hears the products each search of this turn's calls found, the moment it answers, as the tool's `onProducts` does. */
|
|
536
|
+
onProducts?: BuyToolOptions['onProducts'];
|
|
537
|
+
/**
|
|
538
|
+
* Hears each message Agentcard writes for this turn's calls, as the tool's `onText` does, in its place in the turn:
|
|
539
|
+
* after the instant reply and every card handed over before it, paced by `pace`. `turn.items` lists each one as a
|
|
540
|
+
* `text` item, and `onItem` does not hear it.
|
|
541
|
+
*/
|
|
542
|
+
onText?: BuyToolOptions['onText'];
|
|
543
|
+
/** Hears the carts each time a call of this turn builds or changes one, as the tool's `onCart` does. */
|
|
544
|
+
onCart?: BuyToolOptions['onCart'];
|
|
464
545
|
}
|
|
465
546
|
/**
|
|
466
547
|
* The buy tool for one shopper message, from `buy.turn`. It is the same tool, with the same name,
|
|
@@ -515,10 +596,18 @@ export interface BuyToolTurn extends Omit<BuyTool, 'instantAck' | 'instantReacti
|
|
|
515
596
|
* itself for your model, such as the note that the cart changed or a `nextStep` turn's ask for the yes.
|
|
516
597
|
*/
|
|
517
598
|
readonly replyAsWritten: string | null;
|
|
599
|
+
/**
|
|
600
|
+
* With `direct` on: the call this turn's message makes straight to Agentcard, with no model deciding it, and why,
|
|
601
|
+
* or null when your model decides. It is set when the turn opens, from what Agentcard last asked the shopper.
|
|
602
|
+
* Send it with `run(direct.input)`, which returns what your model would read, then show `replyAsWritten` when it
|
|
603
|
+
* is set; when it is null, hand your model the call and its result, and its words answer the shopper.
|
|
604
|
+
*/
|
|
605
|
+
readonly direct: DirectCall | null;
|
|
518
606
|
/**
|
|
519
607
|
* Every message and link the turn handed over, in the order they went out: the instant reply, each
|
|
520
|
-
* link, and each message written through `messageStream()`
|
|
521
|
-
* left out. Return it as the turn's answer, and an app renders it
|
|
608
|
+
* link, each message of Agentcard's that `onText` took, and each message written through `messageStream()`
|
|
609
|
+
* or `say()`. An item its send threw on is left out. Return it as the turn's answer, and an app renders it
|
|
610
|
+
* in order. Each read returns a new array.
|
|
522
611
|
*/
|
|
523
612
|
readonly items: BuyToolTurnItem[];
|
|
524
613
|
/**
|