@agent-cards/commerce-sdk 0.11.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 +167 -10
- package/dist/ack.d.ts +36 -0
- package/dist/ack.js +36 -8
- package/dist/action-links.d.ts +64 -0
- package/dist/action-links.js +77 -0
- package/dist/browser.d.ts +106 -0
- package/dist/browser.js +118 -0
- package/dist/buy-tool.d.ts +218 -26
- package/dist/buy-tool.js +735 -43
- package/dist/client.d.ts +155 -4
- package/dist/client.js +225 -24
- package/dist/definition.js +25 -2
- 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 +7 -5
- package/dist/index.js +6 -4
- package/dist/message.d.ts +4 -1
- package/dist/messages.d.ts +9 -9
- package/dist/messages.js +6 -6
- package/dist/model-view.d.ts +66 -0
- package/dist/model-view.js +207 -5
- package/dist/shopper-message.d.ts +16 -1
- package/dist/shopper-message.js +11 -0
- package/package.json +13 -1
|
@@ -0,0 +1,106 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* `@agent-cards/commerce-sdk/browser`: the part of the package that runs in the shopper's browser.
|
|
3
|
+
* It imports no Node module, so any bundler takes it.
|
|
4
|
+
*
|
|
5
|
+
* `actionWindows` opens each step the shopper takes outside the chat (adding a card, signing in to a
|
|
6
|
+
* store, approving a payment) in a pop-up window over the chat, and closes it the moment the shopper
|
|
7
|
+
* message that ends the step arrives. On a phone, where a browser opens the pop-up as a tab, it
|
|
8
|
+
* closes that tab the same way.
|
|
9
|
+
*/
|
|
10
|
+
import { type ActionLinkKind, type ActionLinkMessage, type ActionLinkOutcome } from './action-links.js';
|
|
11
|
+
export { ACTION_LINK_ENDINGS, ACTION_LINK_KINDS, actionLinkId, actionLinkOutcome, isActionLink, type ActionLinkKind, type ActionLinkMessage, type ActionLinkOutcome, } from './action-links.js';
|
|
12
|
+
/** The part of a window `window.open` returns that `actionWindows` uses. */
|
|
13
|
+
export interface PopupLike {
|
|
14
|
+
readonly closed: boolean;
|
|
15
|
+
close(): void;
|
|
16
|
+
focus(): void;
|
|
17
|
+
}
|
|
18
|
+
/** The part of the page's `window` that `actionWindows` uses. */
|
|
19
|
+
export interface WindowLike {
|
|
20
|
+
open(url: string, target: string, features: string): PopupLike | null;
|
|
21
|
+
readonly screenX?: number;
|
|
22
|
+
readonly screenY?: number;
|
|
23
|
+
readonly outerWidth?: number;
|
|
24
|
+
readonly outerHeight?: number;
|
|
25
|
+
readonly screen?: {
|
|
26
|
+
readonly availWidth?: number;
|
|
27
|
+
readonly availHeight?: number;
|
|
28
|
+
};
|
|
29
|
+
setInterval(handler: () => void, ms: number): unknown;
|
|
30
|
+
clearInterval(id: unknown): void;
|
|
31
|
+
}
|
|
32
|
+
/** A step's link, as the buy tool hands it over (`BuyToolLink`) or as an app passes it on: its kind and its page. */
|
|
33
|
+
export interface ActionLinkLike {
|
|
34
|
+
kind: string;
|
|
35
|
+
url?: string | null;
|
|
36
|
+
/** The store a store login or an approval is for, as the buy tool names it, so a message about another store leaves it open. */
|
|
37
|
+
store?: string | null;
|
|
38
|
+
/** The link's `actionLinkId`, when the buy tool put one on it. Read from the link when absent. */
|
|
39
|
+
id?: string;
|
|
40
|
+
}
|
|
41
|
+
/** A step open in a pop-up. */
|
|
42
|
+
export interface ActionWindow {
|
|
43
|
+
/** The link's `actionLinkId`. */
|
|
44
|
+
readonly id: string;
|
|
45
|
+
readonly kind: ActionLinkKind;
|
|
46
|
+
readonly url: string;
|
|
47
|
+
/** The store the step is for, when the link named one. */
|
|
48
|
+
readonly store: string | null;
|
|
49
|
+
/** False once the window closed, by the shopper or by `actionWindows`. */
|
|
50
|
+
readonly open: boolean;
|
|
51
|
+
}
|
|
52
|
+
export interface ActionWindowsOptions {
|
|
53
|
+
/** The page's window. Defaults to `globalThis.window`; pass one to test with a fake. */
|
|
54
|
+
window?: WindowLike;
|
|
55
|
+
/** The pop-up's size in CSS pixels, kept inside the screen. Defaults to 480 by 760. */
|
|
56
|
+
width?: number;
|
|
57
|
+
height?: number;
|
|
58
|
+
/** How often to check whether the shopper closed a pop-up, in milliseconds. Defaults to 500. */
|
|
59
|
+
pollMs?: number;
|
|
60
|
+
/**
|
|
61
|
+
* Hears a step that ended while its pop-up was open, after `settle` closed the pop-up. Show the step
|
|
62
|
+
* as done here.
|
|
63
|
+
*/
|
|
64
|
+
onEnded?: (step: ActionWindow, outcome: ActionLinkOutcome) => void;
|
|
65
|
+
/**
|
|
66
|
+
* Hears a pop-up the shopper closed before the step ended. The step is still open: keep offering
|
|
67
|
+
* its link, so the shopper can open it again.
|
|
68
|
+
*/
|
|
69
|
+
onClosed?: (step: ActionWindow) => void;
|
|
70
|
+
}
|
|
71
|
+
export interface ActionWindows {
|
|
72
|
+
/**
|
|
73
|
+
* Opens the step's page in a pop-up centered over the chat, or brings its pop-up to the front when
|
|
74
|
+
* one is open already. Returns null when the link is not a step with a page, or when the browser
|
|
75
|
+
* blocked the pop-up: then let the link open the way it would have, as a new tab. Call it from the
|
|
76
|
+
* tap's click handler, so the browser counts the tap as the reason for the pop-up:
|
|
77
|
+
*
|
|
78
|
+
* ```js
|
|
79
|
+
* a.addEventListener('click', (event) => { if (windows.open(link)) event.preventDefault(); });
|
|
80
|
+
* ```
|
|
81
|
+
*/
|
|
82
|
+
open(link: ActionLinkLike): ActionWindow | null;
|
|
83
|
+
/**
|
|
84
|
+
* Hands over a shopper message (`ShopperMessage`, or any object with its `kind` and `facts`, whose
|
|
85
|
+
* `merchantName` keeps one store's message from closing another store's step).
|
|
86
|
+
* Each open pop-up whose step the message ends closes, and `onEnded` hears it. Returns the steps it
|
|
87
|
+
* ended, with how each ended; an empty list when the message ends none.
|
|
88
|
+
*/
|
|
89
|
+
settle(message: ActionLinkMessage): {
|
|
90
|
+
step: ActionWindow;
|
|
91
|
+
outcome: ActionLinkOutcome;
|
|
92
|
+
}[];
|
|
93
|
+
/** The open pop-up for the link, or null. */
|
|
94
|
+
get(link: ActionLinkLike): ActionWindow | null;
|
|
95
|
+
/**
|
|
96
|
+
* Stops watching every pop-up, and leaves each one as it is: a chat that reloads or remounts never
|
|
97
|
+
* shuts the page a shopper is typing a card into. Call it when the chat goes away.
|
|
98
|
+
*/
|
|
99
|
+
dispose(): void;
|
|
100
|
+
}
|
|
101
|
+
/**
|
|
102
|
+
* Opens each step outside the chat in a pop-up and closes it when the step ends. One per chat page.
|
|
103
|
+
* The pop-up keeps its opener, deliberately: a window opened with `noopener` hands back no handle,
|
|
104
|
+
* so the chat could neither close it nor tell when the shopper closed it.
|
|
105
|
+
*/
|
|
106
|
+
export declare function actionWindows(options?: ActionWindowsOptions): ActionWindows;
|
package/dist/browser.js
ADDED
|
@@ -0,0 +1,118 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* `@agent-cards/commerce-sdk/browser`: the part of the package that runs in the shopper's browser.
|
|
3
|
+
* It imports no Node module, so any bundler takes it.
|
|
4
|
+
*
|
|
5
|
+
* `actionWindows` opens each step the shopper takes outside the chat (adding a card, signing in to a
|
|
6
|
+
* store, approving a payment) in a pop-up window over the chat, and closes it the moment the shopper
|
|
7
|
+
* message that ends the step arrives. On a phone, where a browser opens the pop-up as a tab, it
|
|
8
|
+
* closes that tab the same way.
|
|
9
|
+
*/
|
|
10
|
+
import { actionLinkId, actionLinkOutcome, isActionLink } from './action-links.js';
|
|
11
|
+
export { ACTION_LINK_ENDINGS, ACTION_LINK_KINDS, actionLinkId, actionLinkOutcome, isActionLink, } from './action-links.js';
|
|
12
|
+
const DEFAULT_WIDTH = 480;
|
|
13
|
+
const DEFAULT_HEIGHT = 760;
|
|
14
|
+
const DEFAULT_POLL_MS = 500;
|
|
15
|
+
/** `width=…,height=…,left=…,top=…` for a pop-up centered over the opener, kept on its screen. */
|
|
16
|
+
function featuresFor(win, width, height) {
|
|
17
|
+
const availWidth = win.screen?.availWidth ?? width;
|
|
18
|
+
const availHeight = win.screen?.availHeight ?? height;
|
|
19
|
+
const w = Math.max(320, Math.min(width, availWidth));
|
|
20
|
+
const h = Math.max(400, Math.min(height, availHeight));
|
|
21
|
+
const left = Math.max(0, Math.round((win.screenX ?? 0) + ((win.outerWidth ?? w) - w) / 2));
|
|
22
|
+
const top = Math.max(0, Math.round((win.screenY ?? 0) + ((win.outerHeight ?? h) - h) / 2));
|
|
23
|
+
return `popup=yes,width=${w},height=${h},left=${left},top=${top}`;
|
|
24
|
+
}
|
|
25
|
+
const view = (t) => t.step;
|
|
26
|
+
/**
|
|
27
|
+
* Opens each step outside the chat in a pop-up and closes it when the step ends. One per chat page.
|
|
28
|
+
* The pop-up keeps its opener, deliberately: a window opened with `noopener` hands back no handle,
|
|
29
|
+
* so the chat could neither close it nor tell when the shopper closed it.
|
|
30
|
+
*/
|
|
31
|
+
export function actionWindows(options = {}) {
|
|
32
|
+
const open = new Map();
|
|
33
|
+
let timer = null;
|
|
34
|
+
const win = () => options.window ?? (globalThis.window ?? null);
|
|
35
|
+
const stopIfIdle = () => {
|
|
36
|
+
if (open.size === 0 && timer !== null) {
|
|
37
|
+
win()?.clearInterval(timer);
|
|
38
|
+
timer = null;
|
|
39
|
+
}
|
|
40
|
+
};
|
|
41
|
+
const poll = () => {
|
|
42
|
+
for (const [id, tracked] of open) {
|
|
43
|
+
if (!tracked.popup.closed)
|
|
44
|
+
continue;
|
|
45
|
+
open.delete(id);
|
|
46
|
+
tracked.step.open = false;
|
|
47
|
+
options.onClosed?.(view(tracked));
|
|
48
|
+
}
|
|
49
|
+
stopIfIdle();
|
|
50
|
+
};
|
|
51
|
+
const idOf = (link) => link.id ?? actionLinkId(link);
|
|
52
|
+
return {
|
|
53
|
+
open(link) {
|
|
54
|
+
if (!isActionLink(link))
|
|
55
|
+
return null;
|
|
56
|
+
const w = win();
|
|
57
|
+
if (!w)
|
|
58
|
+
return null;
|
|
59
|
+
const id = idOf(link);
|
|
60
|
+
const known = open.get(id);
|
|
61
|
+
if (known && !known.popup.closed) {
|
|
62
|
+
known.popup.focus();
|
|
63
|
+
return view(known);
|
|
64
|
+
}
|
|
65
|
+
let popup = null;
|
|
66
|
+
try {
|
|
67
|
+
// A window named for the step: tapping the link again reaches the same window, never a second one.
|
|
68
|
+
popup = w.open(link.url, `agentcard_${id}`, featuresFor(w, options.width ?? DEFAULT_WIDTH, options.height ?? DEFAULT_HEIGHT));
|
|
69
|
+
}
|
|
70
|
+
catch {
|
|
71
|
+
popup = null;
|
|
72
|
+
}
|
|
73
|
+
if (!popup)
|
|
74
|
+
return null;
|
|
75
|
+
const tracked = { step: { id, kind: link.kind, url: link.url, store: link.store ?? null, open: true }, popup };
|
|
76
|
+
open.set(id, tracked);
|
|
77
|
+
try {
|
|
78
|
+
popup.focus();
|
|
79
|
+
}
|
|
80
|
+
catch {
|
|
81
|
+
/* a browser that refuses focus still shows the window */
|
|
82
|
+
}
|
|
83
|
+
timer ??= w.setInterval(poll, options.pollMs ?? DEFAULT_POLL_MS);
|
|
84
|
+
return view(tracked);
|
|
85
|
+
},
|
|
86
|
+
settle(message) {
|
|
87
|
+
const ended = [];
|
|
88
|
+
for (const [id, tracked] of open) {
|
|
89
|
+
const outcome = actionLinkOutcome(tracked.step, message);
|
|
90
|
+
if (!outcome)
|
|
91
|
+
continue;
|
|
92
|
+
open.delete(id);
|
|
93
|
+
tracked.step.open = false;
|
|
94
|
+
try {
|
|
95
|
+
tracked.popup.close();
|
|
96
|
+
}
|
|
97
|
+
catch {
|
|
98
|
+
/* already gone */
|
|
99
|
+
}
|
|
100
|
+
ended.push({ step: view(tracked), outcome });
|
|
101
|
+
}
|
|
102
|
+
stopIfIdle();
|
|
103
|
+
for (const { step, outcome } of ended)
|
|
104
|
+
options.onEnded?.(step, outcome);
|
|
105
|
+
return ended;
|
|
106
|
+
},
|
|
107
|
+
get(link) {
|
|
108
|
+
if (!isActionLink(link))
|
|
109
|
+
return null;
|
|
110
|
+
const tracked = open.get(idOf(link));
|
|
111
|
+
return tracked && !tracked.popup.closed ? view(tracked) : null;
|
|
112
|
+
},
|
|
113
|
+
dispose() {
|
|
114
|
+
open.clear();
|
|
115
|
+
stopIfIdle();
|
|
116
|
+
},
|
|
117
|
+
};
|
|
118
|
+
}
|
package/dist/buy-tool.d.ts
CHANGED
|
@@ -31,11 +31,12 @@
|
|
|
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 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
|
-
import type
|
|
39
|
+
import { type ShopperMessage } from './shopper-message.js';
|
|
39
40
|
/**
|
|
40
41
|
* Where the purchase stands:
|
|
41
42
|
* - `needs_input`: Agentcard asks the shopper something, or shows a cart.
|
|
@@ -51,13 +52,21 @@ export type BuyToolStatus = 'needs_input' | 'needs_card' | 'approval_sent' | 'pl
|
|
|
51
52
|
* card. `approval`: the page where they approve this purchase. `store_login`:
|
|
52
53
|
* the page where they sign in to the store named in `store`, before Agentcard
|
|
53
54
|
* can buy there. `product`: a product's page at its store, with its `title`,
|
|
54
|
-
* `price`, `productId` and `
|
|
55
|
+
* `price`, `productId`, `imageUrl` and `images`. The page is `url`; `productId` holds no link.
|
|
55
56
|
* A flight option is a `product` link too, with a null `url`, since a flight has no page.
|
|
56
57
|
* `store`: a store's page at the merchant, for a store the shopper can pick, with
|
|
57
58
|
* its name in `title`, `storeId`, `etaMinutes` and `imageUrl`.
|
|
58
59
|
*/
|
|
59
60
|
export interface BuyToolLink {
|
|
60
61
|
kind: 'vault' | 'approval' | 'store_login' | 'product' | 'store';
|
|
62
|
+
/**
|
|
63
|
+
* On a `vault`, `approval` or `store_login` link, a step the shopper takes outside the chat: a stable
|
|
64
|
+
* id for the link (`actionLinkId`), the same each time the same link is handed over, with no part of
|
|
65
|
+
* the URL in it. `actionLinkOutcome(link, message)` says whether a shopper message ended the step,
|
|
66
|
+
* and `actionWindows` from `@agent-cards/commerce-sdk/browser` opens it in a pop-up that closes then.
|
|
67
|
+
* Absent on product and store links.
|
|
68
|
+
*/
|
|
69
|
+
id?: string;
|
|
61
70
|
/**
|
|
62
71
|
* The page the link opens. Null only on a product link for a product with no page at a store, a
|
|
63
72
|
* flight option: show its card with no link to open, and let `productId` pick it.
|
|
@@ -65,11 +74,19 @@ export interface BuyToolLink {
|
|
|
65
74
|
url: string | null;
|
|
66
75
|
/** When the link stops working, as an ISO 8601 timestamp, or null when Agentcard did not say. Always null on a product link. */
|
|
67
76
|
expiresAt: string | null;
|
|
68
|
-
/**
|
|
77
|
+
/**
|
|
78
|
+
* On a store login link: the store's name, or null when Agentcard named none. On an approval link:
|
|
79
|
+
* the store whose cart it approves, when the answer names the cart.
|
|
80
|
+
*/
|
|
69
81
|
store?: string | null;
|
|
70
82
|
/** On a product link: the product's name. */
|
|
71
83
|
title?: string;
|
|
72
|
-
/**
|
|
84
|
+
/**
|
|
85
|
+
* On a product link: what taking the product does, so a button can say it. `reserve` is a table the
|
|
86
|
+
* shopper reserves, `book` a flight, room, class or ticket they book, and `buy` anything they buy.
|
|
87
|
+
*/
|
|
88
|
+
action?: BuyToolProductAction;
|
|
89
|
+
/** On a product link: the price as the shopper reads it, such as `$12.99`, or `from $10.69` for a product priced by a size or another required choice, or null when Agentcard named none. */
|
|
73
90
|
price?: string | null;
|
|
74
91
|
/**
|
|
75
92
|
* On a product link: Agentcard's id for the product, with no link in it. It is the `product_id` the
|
|
@@ -82,6 +99,11 @@ export interface BuyToolLink {
|
|
|
82
99
|
* the merchant. Null when Agentcard sent none. Show it beside the title.
|
|
83
100
|
*/
|
|
84
101
|
imageUrl?: string | null;
|
|
102
|
+
/**
|
|
103
|
+
* On a product link: every photo of the product, `imageUrl` first, at most eight; one entry for a product
|
|
104
|
+
* with one photo and none for a product with none. Show the others as thumbnails that swap the main picture.
|
|
105
|
+
*/
|
|
106
|
+
images?: string[];
|
|
85
107
|
/**
|
|
86
108
|
* On a store link: Agentcard's id for the store, with no link in it. It is the `store_id` the model
|
|
87
109
|
* reads for the same store, and the tool takes it as `store_id`, so a "Order here" button can hand it
|
|
@@ -106,17 +128,27 @@ export interface BuyToolStore {
|
|
|
106
128
|
linkSent: boolean;
|
|
107
129
|
}
|
|
108
130
|
/** A product the last search found. */
|
|
131
|
+
/** What taking a product does: `reserve` a table, `book` a flight, room, class or ticket, `buy` anything else. */
|
|
132
|
+
export type BuyToolProductAction = 'buy' | 'reserve' | 'book';
|
|
133
|
+
/** The action a product of `merchant` takes (BuyToolProductAction). */
|
|
134
|
+
export declare function productAction(merchant: string | null | undefined): BuyToolProductAction;
|
|
109
135
|
export interface BuyToolProduct {
|
|
110
136
|
flight?: BuyFlightItinerary;
|
|
137
|
+
/** What taking it does: `reserve` a table, `book` a flight, room, class or ticket, `buy` anything else. */
|
|
138
|
+
action: BuyToolProductAction;
|
|
111
139
|
id: string;
|
|
112
140
|
/** Agentcard's link-free id for the product, which the model passes as `product_id` when the shopper picks it; null from an older API. */
|
|
113
141
|
ref: string | null;
|
|
114
142
|
name: string;
|
|
115
143
|
/** The price in the minor unit of `currency`. */
|
|
116
144
|
priceCents: number | null;
|
|
145
|
+
/** True when `priceCents` is the lowest price of a product priced by a required choice, which reads `from $10.69`. */
|
|
146
|
+
priceFrom?: true;
|
|
117
147
|
/** The ISO 4217 code the store prices the product in, or null when Agentcard sent none. */
|
|
118
148
|
currency: string | null;
|
|
119
149
|
imageUrl: string | null;
|
|
150
|
+
/** Every photo of the product, `imageUrl` first, at most eight; empty when it has none. */
|
|
151
|
+
images: string[];
|
|
120
152
|
store: string | null;
|
|
121
153
|
/** The product's page at its store, or null when Agentcard sent none. */
|
|
122
154
|
url: string | null;
|
|
@@ -157,6 +189,11 @@ export interface BuyToolResult {
|
|
|
157
189
|
* the request is being worked on. Absent or false when none went out.
|
|
158
190
|
*/
|
|
159
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;
|
|
160
197
|
conversation_id: string | null;
|
|
161
198
|
order_id: string | null;
|
|
162
199
|
decline_code: string | null;
|
|
@@ -165,12 +202,17 @@ export interface BuyToolResult {
|
|
|
165
202
|
next_action: BuyNextAction | null;
|
|
166
203
|
/** The product the model's `product_id` picked, by its catalog id, and whether the cart now holds it; null when the call picked none. */
|
|
167
204
|
pick: BuyPick | null;
|
|
168
|
-
/** The stores the reply presents to choose from, at most
|
|
205
|
+
/** The stores the reply presents to choose from, at most eight, in the order the reply names them; empty when it presents none. */
|
|
169
206
|
stores: BuyToolStore[];
|
|
170
207
|
/** The store the model's `store_id` picked, or the shopper's `the second one` did, and whether Agentcard selected it; null when none was picked. */
|
|
171
208
|
store_pick: BuyStorePick | null;
|
|
172
209
|
/** Every cart hash in this answer. */
|
|
173
210
|
hashes: string[];
|
|
211
|
+
/**
|
|
212
|
+
* The cart the store's checkout is still building after this answer, or null: its cart and total reach the shopper as a
|
|
213
|
+
* message from Agentcard, and only the shopper's yes to that total confirms it.
|
|
214
|
+
*/
|
|
215
|
+
cart_building?: BuyCartBuilding | null;
|
|
174
216
|
/** Agentcard's answer as it came: the purchase answer, or the body of the refusal. */
|
|
175
217
|
envelope: Record<string, unknown>;
|
|
176
218
|
}
|
|
@@ -182,6 +224,18 @@ export interface BuyToolResult {
|
|
|
182
224
|
* - `none`: nothing.
|
|
183
225
|
*/
|
|
184
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
|
+
}
|
|
185
239
|
export interface BuyToolOptions {
|
|
186
240
|
/** A client built with your company's credentials. Build it once and share it across every shopper's tool. */
|
|
187
241
|
agentcard?: AgentcardClient;
|
|
@@ -252,19 +306,75 @@ export interface BuyToolOptions {
|
|
|
252
306
|
*/
|
|
253
307
|
onProgress?: (progress: BuyProgress) => void;
|
|
254
308
|
/**
|
|
255
|
-
*
|
|
256
|
-
*
|
|
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.
|
|
257
353
|
*/
|
|
258
354
|
stream?: false;
|
|
259
355
|
/**
|
|
260
|
-
* `true
|
|
261
|
-
* answers, before Agentcard writes its reply, and the answer then sends only the pages it adds.
|
|
262
|
-
*
|
|
263
|
-
*
|
|
264
|
-
*
|
|
265
|
-
* 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.
|
|
266
361
|
*/
|
|
267
362
|
earlyCards?: boolean;
|
|
363
|
+
/**
|
|
364
|
+
* `true` sets `turn.replyAsWritten` on every turn this tool opens, unless the turn says otherwise:
|
|
365
|
+
* Agentcard's reply, when an answer only asks the shopper something (`replyAsWritten`). Show it as
|
|
366
|
+
* the turn's answer and skip the model call that would reword it. Off by default, so your model
|
|
367
|
+
* writes every answer.
|
|
368
|
+
*/
|
|
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;
|
|
268
378
|
}
|
|
269
379
|
/**
|
|
270
380
|
* The buy tool, in the shape every agent framework takes, because their field
|
|
@@ -338,6 +448,26 @@ export interface BuyTool {
|
|
|
338
448
|
* built with.
|
|
339
449
|
*/
|
|
340
450
|
turn(shopperText: string, options?: BuyToolTurnOptions): BuyToolTurn;
|
|
451
|
+
/**
|
|
452
|
+
* Opens the turn your agent takes on its own once the shopper finished a step outside the chat, such
|
|
453
|
+
* as saving a card or signing in to the store: a message whose `advances` is true. Pass it to your
|
|
454
|
+
* model as you pass `buy.turn`, with `turn.messages(history)` as the messages, and the model's answer
|
|
455
|
+
* is the shopper's next message, the step after the one they took, so they never have to say "ok"
|
|
456
|
+
* or "done" first. Post that answer in place of `message.text`, which the shopper then never reads.
|
|
457
|
+
*
|
|
458
|
+
* The turn moves the purchase only up to the shopper's own decision. Its calls never reach Agentcard
|
|
459
|
+
* with an ask, so nothing in it can place an order, confirm a cart or charge a card for the shopper.
|
|
460
|
+
* Each call reads where the purchase stands and answers with the next step: the card link, or the
|
|
461
|
+
* cart and the ask for the shopper's yes. The one exception is the yes the shopper already gave in the
|
|
462
|
+
* chat for this exact cart, when that confirm stopped for the step they just finished: it goes in for
|
|
463
|
+
* that cart's hash alone, and Agentcard refuses it if the cart changed.
|
|
464
|
+
*
|
|
465
|
+
* Returns null when there is no next step to take: a message whose `advances` is false, a message
|
|
466
|
+
* for another shopper or one whose link closed, a message this tool already took a turn for, and a
|
|
467
|
+
* message that arrives while a call for the shopper's own message is still running, since that
|
|
468
|
+
* call's answer tells the model where the purchase stands. Post `message.text` then, as before.
|
|
469
|
+
*/
|
|
470
|
+
nextStep(message: ShopperMessage, options?: BuyToolTurnOptions): BuyToolTurn | null;
|
|
341
471
|
}
|
|
342
472
|
/**
|
|
343
473
|
* One thing a turn hands the shopper: a message, such as the instant reply or a paragraph your model
|
|
@@ -356,21 +486,34 @@ export interface BuyToolTurnOptions {
|
|
|
356
486
|
* Hears everything the turn hands the shopper, one item at a time and in the order it happened: the
|
|
357
487
|
* instant reply and each message your model writes through `turn.messageStream()` or `turn.say()` as
|
|
358
488
|
* `text` items, and each link as a `link` item. Show each item below the one before it, and the chat
|
|
359
|
-
* reads in the order it happened: a search's cards, then the model's words about them.
|
|
489
|
+
* reads in the order it happened: a search's cards, then the model's words about them. A step link
|
|
490
|
+
* (`vault`, `approval`, `store_login`) is the one exception: it waits for the message that asks for
|
|
491
|
+
* it, and goes out right after that message, so the shopper reads the ask, then taps. Each item
|
|
360
492
|
* waits for the one before it to settle. With `onItem`, the turn hands no link to a `sendLink` and
|
|
361
493
|
* no instant reply to an `onWorking`, the tool's included: pass `onItem`, or those, to the turn.
|
|
362
494
|
*/
|
|
363
495
|
onItem?: (item: BuyToolTurnItem) => void | Promise<void>;
|
|
364
496
|
/**
|
|
365
|
-
* With `onItem`:
|
|
366
|
-
*
|
|
367
|
-
*
|
|
368
|
-
*
|
|
369
|
-
* milliseconds for a message.
|
|
497
|
+
* With `onItem`, `onMessage` or `onText`: how the turn's messages are spaced. Default `false`: each message goes
|
|
498
|
+
* out the moment it is ready. `true` spaces them like someone texting: a message waits until
|
|
499
|
+
* `typingDelay` of it has passed since the item before it went out, and time the model spent writing
|
|
500
|
+
* counts toward the pause. A function returns the pause in milliseconds for a message. Links never wait.
|
|
370
501
|
*/
|
|
371
502
|
pace?: MessagePace;
|
|
372
|
-
/**
|
|
503
|
+
/**
|
|
504
|
+
* `true` sets `turn.replyAsWritten` when an answer only asks the shopper something; `false` leaves it
|
|
505
|
+
* null, so your model rewords every answer. Default: the tool's `replyAsWritten`.
|
|
506
|
+
*/
|
|
507
|
+
replyAsWritten?: boolean;
|
|
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. */
|
|
373
509
|
onTyping?: (typing: boolean) => void;
|
|
510
|
+
/**
|
|
511
|
+
* Without `onItem`: hears each message your model writes through `turn.messageStream()` or
|
|
512
|
+
* `turn.say()`, paced by `pace`, in the same order as the links `sendLink` hears. Write the model's
|
|
513
|
+
* text through the turn, and a step link (`vault`, `approval`, `store_login`) reaches `sendLink`
|
|
514
|
+
* after the message that asks for it, never before.
|
|
515
|
+
*/
|
|
516
|
+
onMessage?: (message: string) => void | Promise<void>;
|
|
374
517
|
/**
|
|
375
518
|
* Delivers a link of this turn to the shopper, as the tool's `sendLink` does. Each call waits for it.
|
|
376
519
|
* Without one, `turn.links` still takes each link, and the model reads that it went out, so return
|
|
@@ -385,6 +528,20 @@ export interface BuyToolTurnOptions {
|
|
|
385
528
|
onResult?: (result: BuyToolResult) => void | Promise<void>;
|
|
386
529
|
/** Called with each stage Agentcard reaches on a call this turn makes. */
|
|
387
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'];
|
|
388
545
|
}
|
|
389
546
|
/**
|
|
390
547
|
* The buy tool for one shopper message, from `buy.turn`. It is the same tool, with the same name,
|
|
@@ -392,7 +549,15 @@ export interface BuyToolTurnOptions {
|
|
|
392
549
|
* belong to this turn alone. `ack`, `links` and `results` are read-only, and JSON leaves them out, so a
|
|
393
550
|
* tool runner that sends the tool to its API sends the same fields as before.
|
|
394
551
|
*/
|
|
395
|
-
export interface BuyToolTurn extends Omit<BuyTool, 'instantAck' | 'instantReaction' | 'turn'> {
|
|
552
|
+
export interface BuyToolTurn extends Omit<BuyTool, 'instantAck' | 'instantReaction' | 'turn' | 'nextStep'> {
|
|
553
|
+
/** For a turn `buy.nextStep` opened, the message whose step the shopper finished; null for a turn of a shopper message. */
|
|
554
|
+
readonly event: ShopperMessage | null;
|
|
555
|
+
/**
|
|
556
|
+
* For a turn `buy.nextStep` opened that your agent could not finish: call it before you post `message.text` instead.
|
|
557
|
+
* The message's text then reaches later turns as words the shopper read (`agentcard.delivered`), and a later
|
|
558
|
+
* `buy.nextStep` for the same event opens a turn again, as on a webhook's redelivery. Does nothing on another turn.
|
|
559
|
+
*/
|
|
560
|
+
release(): void;
|
|
396
561
|
/** The instant reply this turn sent to `onWorking` when it opened, or null when it sent none, as with a reaction. */
|
|
397
562
|
readonly ack: string | null;
|
|
398
563
|
/** The reaction this turn put on the shopper's message, `👀`, when it opened or once a call ran slow, or null while it put none or when `onReaction` threw. */
|
|
@@ -422,19 +587,40 @@ export interface BuyToolTurn extends Omit<BuyTool, 'instantAck' | 'instantReacti
|
|
|
422
587
|
readonly links: BuyToolLink[];
|
|
423
588
|
/** The whole answer of each call this turn made, in the order the calls returned. Each read returns a new array. */
|
|
424
589
|
readonly results: BuyToolResult[];
|
|
590
|
+
/**
|
|
591
|
+
* With `replyAsWritten` on: Agentcard's reply when the turn's last answer only asks the shopper
|
|
592
|
+
* something, such as a product list, a size or a cart's total and the ask for their yes, with every
|
|
593
|
+
* link taken out. Read it after the tool runs. When it is set, show it with `say()` and end the turn
|
|
594
|
+
* without calling your model again: the reply already says what the model would. Null when the option
|
|
595
|
+
* is off, before any call, after an answer your model has to word, and after a line the tool wrote
|
|
596
|
+
* itself for your model, such as the note that the cart changed or a `nextStep` turn's ask for the yes.
|
|
597
|
+
*/
|
|
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;
|
|
425
606
|
/**
|
|
426
607
|
* Every message and link the turn handed over, in the order they went out: the instant reply, each
|
|
427
|
-
* link, and each message written through `messageStream()`
|
|
428
|
-
* 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.
|
|
429
611
|
*/
|
|
430
612
|
readonly items: BuyToolTurnItem[];
|
|
431
613
|
/**
|
|
432
614
|
* A message stream whose messages become this turn's `text` items, after the items before them, as
|
|
433
615
|
* `messageStream` cuts them: push your model's text as it streams, then `end` it. Start one for each
|
|
434
|
-
* model message. With `onItem`, the turn spaces them by its `pace`.
|
|
616
|
+
* model message. With `onItem`, the turn spaces them by its `pace`. `end` resolves once the messages and the step links
|
|
617
|
+
* held for them went out, and rejects with the first that failed to send.
|
|
435
618
|
*/
|
|
436
619
|
messageStream(): MessageStream;
|
|
437
|
-
/**
|
|
620
|
+
/**
|
|
621
|
+
* Cuts a whole text into messages, as `toMessages` does, and hands each one over as a `text` item. Resolves once every
|
|
622
|
+
* one, and every step link held for it, went out, and rejects with the first that failed to send.
|
|
623
|
+
*/
|
|
438
624
|
say(text: string): Promise<void>;
|
|
439
625
|
}
|
|
440
626
|
/** A message in the one shape the Anthropic, OpenAI and Vercel AI SDK message lists all take. */
|
|
@@ -452,6 +638,12 @@ export type BuyToolTurnMessage = {
|
|
|
452
638
|
* or an OpenAI Responses item, they come as an assistant message of their own.
|
|
453
639
|
*/
|
|
454
640
|
export declare function turnMessages<M extends object>(history: readonly M[] | null | undefined, told: readonly string[], shopperText: string): Array<M | BuyToolTurnMessage>;
|
|
641
|
+
/**
|
|
642
|
+
* The words a `nextStep` turn hands the model as the user's message: the event, marked as no one's
|
|
643
|
+
* typing, and the instruction to take the next step now in one short message, without the filler of
|
|
644
|
+
* a step announced and a question about what comes next.
|
|
645
|
+
*/
|
|
646
|
+
export declare function nextStepPrompt(message: ShopperMessage): string;
|
|
455
647
|
/** Shared with the merchant monitor so link eligibility uses the tool's product mapping. */
|
|
456
648
|
export declare function productsOf(turn: BuyTurn): BuyToolProduct[];
|
|
457
649
|
/**
|