@agent-cards/commerce-sdk 0.9.0 → 0.11.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.
@@ -21,10 +21,21 @@
21
21
  * still finishing gets one 12-second pause and one read of the conversation.
22
22
  * A turn that outlived Agentcard's budget gets a read every 5 seconds for up
23
23
  * to a minute, until the turn ends.
24
+ *
25
+ * `turn` binds the tool to one shopper message: the calls the model makes
26
+ * through it hand their links, answers and instant reply to that turn's own
27
+ * callbacks and lists, so a shopper who sends a second message before the
28
+ * first is answered never gets one message's cards beside the other's reply.
29
+ * Every turn of one tool still shares the shopper's conversation state, such
30
+ * as the store pages already sent. A turn with `onItem` hands the app its
31
+ * messages and links as one sequence, in the order they happened, and spaces
32
+ * the messages like someone texting.
24
33
  */
25
34
  import { AgentcardClient, type BuyCart, type BuyFlightItinerary, type BuyDeliveryAddress, type BuyNextAction, type BuyPick, type BuyProgress, type BuyStorePick, type BuyTurn, type FetchLike } from './client.js';
26
35
  import { type BuyToolDefinition, type BuyToolInput, type BuyToolInputSchema, type BuyToolStandardSchema, type OpenAIBuyTool } from './definition.js';
27
36
  import { type BuyToolModelView } from './model-view.js';
37
+ import { type MessagePace, type MessageStream } from './messages.js';
38
+ import type { ShopperMessage } from './shopper-message.js';
28
39
  /**
29
40
  * Where the purchase stands:
30
41
  * - `needs_input`: Agentcard asks the shopper something, or shows a cart.
@@ -41,12 +52,17 @@ export type BuyToolStatus = 'needs_input' | 'needs_card' | 'approval_sent' | 'pl
41
52
  * the page where they sign in to the store named in `store`, before Agentcard
42
53
  * can buy there. `product`: a product's page at its store, with its `title`,
43
54
  * `price`, `productId` and `imageUrl`. The page is `url`; `productId` holds no link.
55
+ * A flight option is a `product` link too, with a null `url`, since a flight has no page.
44
56
  * `store`: a store's page at the merchant, for a store the shopper can pick, with
45
- * its name in `title`, `storeId` and `etaMinutes`.
57
+ * its name in `title`, `storeId`, `etaMinutes` and `imageUrl`.
46
58
  */
47
59
  export interface BuyToolLink {
48
60
  kind: 'vault' | 'approval' | 'store_login' | 'product' | 'store';
49
- url: string;
61
+ /**
62
+ * The page the link opens. Null only on a product link for a product with no page at a store, a
63
+ * flight option: show its card with no link to open, and let `productId` pick it.
64
+ */
65
+ url: string | null;
50
66
  /** When the link stops working, as an ISO 8601 timestamp, or null when Agentcard did not say. Always null on a product link. */
51
67
  expiresAt: string | null;
52
68
  /** On a store login link: the store's name, or null when Agentcard named none. */
@@ -61,7 +77,10 @@ export interface BuyToolLink {
61
77
  * can hand it back as the shopper's pick. Absent when Agentcard sent no such id, as an older API does.
62
78
  */
63
79
  productId?: string;
64
- /** On a product link: the product's picture, or null when Agentcard sent none. Show it beside the title. */
80
+ /**
81
+ * On a product link: the product's picture. On a store link: the picture the store's card shows at
82
+ * the merchant. Null when Agentcard sent none. Show it beside the title.
83
+ */
65
84
  imageUrl?: string | null;
66
85
  /**
67
86
  * On a store link: Agentcard's id for the store, with no link in it. It is the `store_id` the model
@@ -81,6 +100,8 @@ export interface BuyToolStore {
81
100
  url: string | null;
82
101
  /** The delivery estimate in minutes, or null when the store showed none. */
83
102
  etaMinutes: number | null;
103
+ /** The picture the store's card shows at the merchant, or null when Agentcard sent none. */
104
+ imageUrl: string | null;
84
105
  /** True when the shopper holds this store's page: `sendLink` took it in this call or an earlier call of the same model turn. */
85
106
  linkSent: boolean;
86
107
  }
@@ -131,8 +152,9 @@ export interface BuyToolResult {
131
152
  /** True when Agentcard tells the shopper how the order went, so the shopper need not reply. */
132
153
  shopper_need_not_reply: boolean;
133
154
  /**
134
- * True when `onWorking` delivered this turn's instant reply, such as `got it, finding a taco`, so the
135
- * shopper already heard that the request is being worked on. Absent or false when none went out.
155
+ * True when this turn's acknowledgement reached the shopper: the 👀 `onReaction` put on their message,
156
+ * or the instant reply `onWorking` sent, such as `got it, finding a taco`. The shopper already knows
157
+ * the request is being worked on. Absent or false when none went out.
136
158
  */
137
159
  shopper_already_told_looking?: boolean;
138
160
  conversation_id: string | null;
@@ -152,6 +174,14 @@ export interface BuyToolResult {
152
174
  /** Agentcard's answer as it came: the purchase answer, or the body of the refusal. */
153
175
  envelope: Record<string, unknown>;
154
176
  }
177
+ /**
178
+ * How the tool acknowledges a shopper's message while Agentcard works on it:
179
+ * - `reaction`, the default: a 👀 on the shopper's own message, through `onReaction`. No text goes out.
180
+ * - `text`: an instant reply, such as `on it, looking for usb-c cables under $20`, through `onWorking`,
181
+ * for SMS, or any chat that shows no reactions.
182
+ * - `none`: nothing.
183
+ */
184
+ export type BuyAckMode = 'reaction' | 'text' | 'none';
155
185
  export interface BuyToolOptions {
156
186
  /** A client built with your company's credentials. Build it once and share it across every shopper's tool. */
157
187
  agentcard?: AgentcardClient;
@@ -164,7 +194,9 @@ export interface BuyToolOptions {
164
194
  * holds the link before the model answers. A `sendLink` that throws on a
165
195
  * Vault or approval link makes the call throw too, and the model never
166
196
  * reads that a link went out. One that throws on a product link leaves
167
- * that product's `linkSent` false, and the call goes on.
197
+ * that product's `linkSent` false, and the call goes on. It hears the links of every call, whatever
198
+ * shopper message the call answers: a chat app where a shopper can send a second message before the
199
+ * first is answered passes `sendLink` to `buy.turn` instead, so each link reaches the turn it belongs to.
168
200
  */
169
201
  sendLink?: (link: BuyToolLink) => void | Promise<void>;
170
202
  /** Where the orders go, when you already hold the shopper's address. The model never sets it. */
@@ -175,23 +207,43 @@ export interface BuyToolOptions {
175
207
  fetch?: FetchLike;
176
208
  /** The pause between two reads of the conversation. Default: a timer. */
177
209
  wait?: (ms: number) => Promise<void>;
210
+ /**
211
+ * Reacts to the shopper's message with `emoji`, always `👀`, to say the tool is on it: show it on the
212
+ * shopper's own bubble, as a tapback. It fires once per message: the moment `buy.turn` opens on a
213
+ * search, or, for a message that got none, such as `yes`, once a call passes `ackDelayMs`. Never on a
214
+ * pick, an address, an answer to a question or a step toward checkout when the turn opens. With it,
215
+ * the tool sends no text acknowledgement at all, unless `ack` is `'text'`. One that throws costs
216
+ * nothing but the reaction.
217
+ */
218
+ onReaction?: (emoji: string) => void | Promise<void>;
178
219
  /**
179
220
  * Sends the shopper an instant reply, such as `on it, looking for usb-c cables under $20`, while
180
- * Agentcard works on a new request. It goes out when Agentcard has not answered within
181
- * `ackDelayMs`, once for the calls the model makes in one turn, and never on a confirm, a pick or a
182
- * reply such as `yes`, `2` or a code. The call waits for it before it returns, so the shopper reads
183
- * it before any link or the model's answer. One that throws costs nothing but the instant reply.
221
+ * Agentcard works on a new request, when the tool acknowledges in text: with `ack: 'text'`, or with
222
+ * no `onReaction`. It goes out when Agentcard has not answered within
223
+ * `ackDelayMs`, once for the calls the model makes in one turn. A slow pick gets `adding it now`
224
+ * for a product or `got it, one moment` for a store or a place in a list. A slow confirm gets
225
+ * `on it, one moment`. These replies name nothing the shopper said. A code gets no reply.
226
+ * The call waits for it before it returns, so the shopper reads it before any link or the model's
227
+ * answer. One that throws costs nothing but the instant reply.
184
228
  */
185
229
  onWorking?: (message: string) => void | Promise<void>;
186
- /** `false` sends no instant reply. A function writes it from the shopper's words instead, and an empty answer sends none. A turn whose message `instantAck` sent nothing for gets the built-in reply that names nothing. */
187
- ack?: false | ((ask: string) => string | null | undefined);
188
- /** How long Agentcard has to answer before the instant reply goes out. Default 1500. */
230
+ /**
231
+ * How the tool acknowledges a message (`BuyAckMode`). Left out, it reacts through `onReaction` when
232
+ * the tool or the turn has one, and otherwise sends text through `onWorking`, as 0.9 did. `'text'`
233
+ * sends text even with an `onReaction`, and `'none'`, or `false`, sends nothing. A function is
234
+ * `'text'` with your own words: it writes the instant reply from the shopper's words, and an empty
235
+ * answer sends none. A turn whose message `instantAck` sent nothing for gets the built-in reply that
236
+ * names nothing.
237
+ */
238
+ ack?: BuyAckMode | false | ((ask: string) => string | null | undefined);
239
+ /** How long Agentcard has to answer before the instant reply or the reaction goes out. Default 1500. */
189
240
  ackDelayMs?: number;
190
241
  /**
191
242
  * Called with the whole answer once per call, after its links went out and before the model reads
192
243
  * it, so your app can show the carts, the status, `next_action` or the order id without wrapping the
193
244
  * tool. A refusal the tool turns into an answer, such as a Vault link for a shopper with no card,
194
- * counts; a call that throws calls nothing. One that throws costs nothing but the call to it.
245
+ * counts; a call that throws calls nothing. One that throws costs nothing but the call to it. Like
246
+ * `sendLink`, it hears every call; pass it to `buy.turn` to hear only the answers of one turn.
195
247
  */
196
248
  onResult?: (result: BuyToolResult) => void | Promise<void>;
197
249
  /**
@@ -258,14 +310,148 @@ export interface BuyTool {
258
310
  * The instant reply for a shopper's message, such as `on it, looking for usb-c cables under $20`, or
259
311
  * null for a message that starts no search: `yes`, `1`, `ok`, a code, `hi`, or the shopper's own
260
312
  * details, such as a name, a phone number or an address, and any message that answers a request for
261
- * shipping or guest details or a code, and a pick of a product the shopper saw. Call it the moment the
313
+ * shipping or guest details or a code, and a pick of a product or a store the shopper saw, such as
314
+ * `Order from Tacos El Grullense` as a store card sends it. Call it the moment the
262
315
  * message arrives, before your model runs, and send the text it returns. When it returns a reply, the
263
316
  * tool sends none of its own through `onWorking` for that turn, and the model reads
264
317
  * `shopper_already_told_looking`, so the shopper never hears it twice. When it returns null for a
265
318
  * message that picks nothing, such as `yes`, a slow call still sends `onWorking` a reply that names nothing.
266
319
  */
267
320
  instantAck(shopperText: string): string | null;
321
+ /**
322
+ * The reaction for a shopper's message, `👀`, or null for one that starts no search, read as
323
+ * `instantAck` reads it. Call it the moment the message arrives, before your model runs, and put the
324
+ * emoji it returns on the shopper's message. When it returns one, the tool sends no reaction or text of
325
+ * its own for that turn, and the model reads `shopper_already_told_looking`. With `buy.turn` and
326
+ * `onReaction`, the turn does this for you.
327
+ */
328
+ instantReaction(shopperText: string): string | null;
329
+ /**
330
+ * Opens the turn of one shopper message and returns the tool for that turn: pass it to your model in
331
+ * place of the tool, as `tools: [turn]`. Each call the model makes through it hands its links to this
332
+ * turn's `sendLink` and keeps them in `turn.links`, hands its answer to this turn's `onResult` and
333
+ * keeps it in `turn.results`, and sends this turn's instant reply to its `onWorking`. A shopper who
334
+ * sends a second message while the first still runs gets a second turn, and no link, cart or reply
335
+ * of one reaches the other. Call it the moment the message arrives, before your model runs: with an
336
+ * `onReaction`, a search gets its 👀 at once, and with only an `onWorking`, the instant reply
337
+ * `instantAck` would return goes out at once. An option left out falls back to the one the tool was
338
+ * built with.
339
+ */
340
+ turn(shopperText: string, options?: BuyToolTurnOptions): BuyToolTurn;
268
341
  }
342
+ /**
343
+ * One thing a turn hands the shopper: a message, such as the instant reply or a paragraph your model
344
+ * wrote, or a link, such as a product card. `buy.turn`'s `onItem` hears them in the order they happened.
345
+ */
346
+ export type BuyToolTurnItem = {
347
+ kind: 'text';
348
+ text: string;
349
+ } | {
350
+ kind: 'link';
351
+ link: BuyToolLink;
352
+ };
353
+ /** The callbacks of one turn. Each one left out falls back to the option of the same name the tool was built with, except with `onItem`. */
354
+ export interface BuyToolTurnOptions {
355
+ /**
356
+ * Hears everything the turn hands the shopper, one item at a time and in the order it happened: the
357
+ * instant reply and each message your model writes through `turn.messageStream()` or `turn.say()` as
358
+ * `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. Each item
360
+ * waits for the one before it to settle. With `onItem`, the turn hands no link to a `sendLink` and
361
+ * no instant reply to an `onWorking`, the tool's included: pass `onItem`, or those, to the turn.
362
+ */
363
+ onItem?: (item: BuyToolTurnItem) => void | Promise<void>;
364
+ /**
365
+ * With `onItem`: spaces the turn's messages like someone texting. A message waits until
366
+ * `typingDelay` of it has passed since the item before it went out, so the shopper reads one message
367
+ * at a time. Time the model spent writing counts toward the pause. Links never wait. Default `true`;
368
+ * `false` hands each message over the moment it is ready, and a function returns the pause in
369
+ * milliseconds for a message.
370
+ */
371
+ pace?: MessagePace;
372
+ /** With `onItem`: hears `true` when the pause before a message starts and `false` the moment that message goes out, for a typing indicator. */
373
+ onTyping?: (typing: boolean) => void;
374
+ /**
375
+ * Delivers a link of this turn to the shopper, as the tool's `sendLink` does. Each call waits for it.
376
+ * Without one, `turn.links` still takes each link, and the model reads that it went out, so return
377
+ * `turn.links` beside the turn's reply.
378
+ */
379
+ sendLink?: (link: BuyToolLink) => void | Promise<void>;
380
+ /** Reacts 👀 to this turn's message: at once when `turn` opens on a new request, or once a slow call passes `ackDelayMs`. */
381
+ onReaction?: (emoji: string) => void | Promise<void>;
382
+ /** Sends this turn's instant reply in text, when the tool acknowledges in text: at once when `turn` opens on a new request, or once a slow call passes `ackDelayMs`. */
383
+ onWorking?: (message: string) => void | Promise<void>;
384
+ /** Called with the whole answer of each call this turn makes, after its links went out and before the model reads it. */
385
+ onResult?: (result: BuyToolResult) => void | Promise<void>;
386
+ /** Called with each stage Agentcard reaches on a call this turn makes. */
387
+ onProgress?: (progress: BuyProgress) => void;
388
+ }
389
+ /**
390
+ * The buy tool for one shopper message, from `buy.turn`. It is the same tool, with the same name,
391
+ * schema and methods, so a framework takes it as it takes the tool, and the calls made through it
392
+ * belong to this turn alone. `ack`, `links` and `results` are read-only, and JSON leaves them out, so a
393
+ * tool runner that sends the tool to its API sends the same fields as before.
394
+ */
395
+ export interface BuyToolTurn extends Omit<BuyTool, 'instantAck' | 'instantReaction' | 'turn'> {
396
+ /** The instant reply this turn sent to `onWorking` when it opened, or null when it sent none, as with a reaction. */
397
+ readonly ack: string | null;
398
+ /** 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. */
399
+ readonly reaction: string | null;
400
+ /**
401
+ * The messages Agentcard posted into this shopper's chat that the model has not read yet, oldest
402
+ * first, such as `card_added` once they saved a card: each one `agentcard.listen`'s or
403
+ * `agentcardWebhook`'s `onMessage` returned from, on the client this tool was built with. Once
404
+ * `messages` ran, the ones it handed the model.
405
+ */
406
+ readonly agentcardMessages: ShopperMessage[];
407
+ /**
408
+ * What to send your model for this turn: `history`, then what Agentcard told the shopper that the
409
+ * history does not hold yet, as the assistant's words with every link taken out, then the shopper's
410
+ * message as a user message. Pass it as the model's `messages` and save what the model's run ends
411
+ * with as the next history, so the model reads that the shopper saved a card, approved or got an
412
+ * order before it answers them. A message the history already holds goes in no second time, and one
413
+ * a failed turn never saved goes in again. The Anthropic, OpenAI and Vercel AI SDK message lists all
414
+ * take it. Agentcard's words join the history's last assistant message when it is plain text, and
415
+ * come as an assistant message of their own otherwise. `history` is never changed.
416
+ */
417
+ messages<M extends object = BuyToolTurnMessage>(history?: readonly M[] | null): Array<M | BuyToolTurnMessage>;
418
+ /**
419
+ * Every link this turn's calls handed over, in order: the Vault, approval and store login links,
420
+ * each product's store page and each store card's page. A link `sendLink` threw on is left out. Each read returns a new array.
421
+ */
422
+ readonly links: BuyToolLink[];
423
+ /** The whole answer of each call this turn made, in the order the calls returned. Each read returns a new array. */
424
+ readonly results: BuyToolResult[];
425
+ /**
426
+ * 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()` or `say()`. An item its send threw on is
428
+ * left out. Return it as the turn's answer, and an app renders it in order. Each read returns a new array.
429
+ */
430
+ readonly items: BuyToolTurnItem[];
431
+ /**
432
+ * A message stream whose messages become this turn's `text` items, after the items before them, as
433
+ * `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`.
435
+ */
436
+ messageStream(): MessageStream;
437
+ /** Cuts a whole text into messages, as `toMessages` does, and hands each one over as a `text` item. Resolves once every one went out. */
438
+ say(text: string): Promise<void>;
439
+ }
440
+ /** A message in the one shape the Anthropic, OpenAI and Vercel AI SDK message lists all take. */
441
+ export type BuyToolTurnMessage = {
442
+ role: 'assistant';
443
+ content: string;
444
+ } | {
445
+ role: 'user';
446
+ content: string;
447
+ };
448
+ /**
449
+ * The model's messages for a turn: the history, what Agentcard told the shopper, then the shopper's
450
+ * words. Agentcard's words join a last assistant message of plain text, a string or text parts only,
451
+ * so no provider reads two assistant messages in a row. After anything else, such as a tool result
452
+ * or an OpenAI Responses item, they come as an assistant message of their own.
453
+ */
454
+ export declare function turnMessages<M extends object>(history: readonly M[] | null | undefined, told: readonly string[], shopperText: string): Array<M | BuyToolTurnMessage>;
269
455
  /** Shared with the merchant monitor so link eligibility uses the tool's product mapping. */
270
456
  export declare function productsOf(turn: BuyTurn): BuyToolProduct[];
271
457
  /**