@agent-cards/commerce-sdk 0.9.0 → 0.10.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
  }
@@ -164,7 +185,9 @@ export interface BuyToolOptions {
164
185
  * holds the link before the model answers. A `sendLink` that throws on a
165
186
  * Vault or approval link makes the call throw too, and the model never
166
187
  * 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.
188
+ * that product's `linkSent` false, and the call goes on. It hears the links of every call, whatever
189
+ * shopper message the call answers: a chat app where a shopper can send a second message before the
190
+ * first is answered passes `sendLink` to `buy.turn` instead, so each link reaches the turn it belongs to.
168
191
  */
169
192
  sendLink?: (link: BuyToolLink) => void | Promise<void>;
170
193
  /** Where the orders go, when you already hold the shopper's address. The model never sets it. */
@@ -178,9 +201,11 @@ export interface BuyToolOptions {
178
201
  /**
179
202
  * Sends the shopper an instant reply, such as `on it, looking for usb-c cables under $20`, while
180
203
  * 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.
204
+ * `ackDelayMs`, once for the calls the model makes in one turn. A slow pick gets `adding it now`
205
+ * for a product or `got it, one moment` for a store or a place in a list. A slow confirm gets
206
+ * `on it, one moment`. These replies name nothing the shopper said. A code gets no reply.
207
+ * The call waits for it before it returns, so the shopper reads it before any link or the model's
208
+ * answer. One that throws costs nothing but the instant reply.
184
209
  */
185
210
  onWorking?: (message: string) => void | Promise<void>;
186
211
  /** `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. */
@@ -191,7 +216,8 @@ export interface BuyToolOptions {
191
216
  * Called with the whole answer once per call, after its links went out and before the model reads
192
217
  * it, so your app can show the carts, the status, `next_action` or the order id without wrapping the
193
218
  * 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.
219
+ * counts; a call that throws calls nothing. One that throws costs nothing but the call to it. Like
220
+ * `sendLink`, it hears every call; pass it to `buy.turn` to hear only the answers of one turn.
195
221
  */
196
222
  onResult?: (result: BuyToolResult) => void | Promise<void>;
197
223
  /**
@@ -258,14 +284,135 @@ export interface BuyTool {
258
284
  * The instant reply for a shopper's message, such as `on it, looking for usb-c cables under $20`, or
259
285
  * null for a message that starts no search: `yes`, `1`, `ok`, a code, `hi`, or the shopper's own
260
286
  * 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
287
+ * shipping or guest details or a code, and a pick of a product or a store the shopper saw, such as
288
+ * `Order from Tacos El Grullense` as a store card sends it. Call it the moment the
262
289
  * message arrives, before your model runs, and send the text it returns. When it returns a reply, the
263
290
  * tool sends none of its own through `onWorking` for that turn, and the model reads
264
291
  * `shopper_already_told_looking`, so the shopper never hears it twice. When it returns null for a
265
292
  * message that picks nothing, such as `yes`, a slow call still sends `onWorking` a reply that names nothing.
266
293
  */
267
294
  instantAck(shopperText: string): string | null;
295
+ /**
296
+ * Opens the turn of one shopper message and returns the tool for that turn: pass it to your model in
297
+ * place of the tool, as `tools: [turn]`. Each call the model makes through it hands its links to this
298
+ * turn's `sendLink` and keeps them in `turn.links`, hands its answer to this turn's `onResult` and
299
+ * keeps it in `turn.results`, and sends this turn's instant reply to its `onWorking`. A shopper who
300
+ * sends a second message while the first still runs gets a second turn, and no link, cart or reply
301
+ * of one reaches the other. Call it the moment the message arrives, before your model runs: with an
302
+ * `onWorking`, the instant reply `instantAck` would return goes out at once. An option left out falls
303
+ * back to the one the tool was built with.
304
+ */
305
+ turn(shopperText: string, options?: BuyToolTurnOptions): BuyToolTurn;
268
306
  }
307
+ /**
308
+ * One thing a turn hands the shopper: a message, such as the instant reply or a paragraph your model
309
+ * wrote, or a link, such as a product card. `buy.turn`'s `onItem` hears them in the order they happened.
310
+ */
311
+ export type BuyToolTurnItem = {
312
+ kind: 'text';
313
+ text: string;
314
+ } | {
315
+ kind: 'link';
316
+ link: BuyToolLink;
317
+ };
318
+ /** 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`. */
319
+ export interface BuyToolTurnOptions {
320
+ /**
321
+ * Hears everything the turn hands the shopper, one item at a time and in the order it happened: the
322
+ * instant reply and each message your model writes through `turn.messageStream()` or `turn.say()` as
323
+ * `text` items, and each link as a `link` item. Show each item below the one before it, and the chat
324
+ * reads in the order it happened: a search's cards, then the model's words about them. Each item
325
+ * waits for the one before it to settle. With `onItem`, the turn hands no link to a `sendLink` and
326
+ * no instant reply to an `onWorking`, the tool's included: pass `onItem`, or those, to the turn.
327
+ */
328
+ onItem?: (item: BuyToolTurnItem) => void | Promise<void>;
329
+ /**
330
+ * With `onItem`: spaces the turn's messages like someone texting. A message waits until
331
+ * `typingDelay` of it has passed since the item before it went out, so the shopper reads one message
332
+ * at a time. Time the model spent writing counts toward the pause. Links never wait. Default `true`;
333
+ * `false` hands each message over the moment it is ready, and a function returns the pause in
334
+ * milliseconds for a message.
335
+ */
336
+ pace?: MessagePace;
337
+ /** With `onItem`: hears `true` when the pause before a message starts and `false` the moment that message goes out, for a typing indicator. */
338
+ onTyping?: (typing: boolean) => void;
339
+ /**
340
+ * Delivers a link of this turn to the shopper, as the tool's `sendLink` does. Each call waits for it.
341
+ * Without one, `turn.links` still takes each link, and the model reads that it went out, so return
342
+ * `turn.links` beside the turn's reply.
343
+ */
344
+ sendLink?: (link: BuyToolLink) => void | Promise<void>;
345
+ /** Sends this turn's instant reply: at once when `turn` opens on a new request, or once a slow call passes `ackDelayMs`. */
346
+ onWorking?: (message: string) => void | Promise<void>;
347
+ /** Called with the whole answer of each call this turn makes, after its links went out and before the model reads it. */
348
+ onResult?: (result: BuyToolResult) => void | Promise<void>;
349
+ /** Called with each stage Agentcard reaches on a call this turn makes. */
350
+ onProgress?: (progress: BuyProgress) => void;
351
+ }
352
+ /**
353
+ * The buy tool for one shopper message, from `buy.turn`. It is the same tool, with the same name,
354
+ * schema and methods, so a framework takes it as it takes the tool, and the calls made through it
355
+ * belong to this turn alone. `ack`, `links` and `results` are read-only, and JSON leaves them out, so a
356
+ * tool runner that sends the tool to its API sends the same fields as before.
357
+ */
358
+ export interface BuyToolTurn extends Omit<BuyTool, 'instantAck' | 'turn'> {
359
+ /** The instant reply this turn sent to `onWorking` when it opened, or null when it sent none. */
360
+ readonly ack: string | null;
361
+ /**
362
+ * The messages Agentcard posted into this shopper's chat that the model has not read yet, oldest
363
+ * first, such as `card_added` once they saved a card: each one `agentcard.listen`'s or
364
+ * `agentcardWebhook`'s `onMessage` returned from, on the client this tool was built with. Once
365
+ * `messages` ran, the ones it handed the model.
366
+ */
367
+ readonly agentcardMessages: ShopperMessage[];
368
+ /**
369
+ * What to send your model for this turn: `history`, then what Agentcard told the shopper that the
370
+ * history does not hold yet, as the assistant's words with every link taken out, then the shopper's
371
+ * message as a user message. Pass it as the model's `messages` and save what the model's run ends
372
+ * with as the next history, so the model reads that the shopper saved a card, approved or got an
373
+ * order before it answers them. A message the history already holds goes in no second time, and one
374
+ * a failed turn never saved goes in again. The Anthropic, OpenAI and Vercel AI SDK message lists all
375
+ * take it. Agentcard's words join the history's last assistant message when it is plain text, and
376
+ * come as an assistant message of their own otherwise. `history` is never changed.
377
+ */
378
+ messages<M extends object = BuyToolTurnMessage>(history?: readonly M[] | null): Array<M | BuyToolTurnMessage>;
379
+ /**
380
+ * Every link this turn's calls handed over, in order: the Vault, approval and store login links,
381
+ * 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.
382
+ */
383
+ readonly links: BuyToolLink[];
384
+ /** The whole answer of each call this turn made, in the order the calls returned. Each read returns a new array. */
385
+ readonly results: BuyToolResult[];
386
+ /**
387
+ * Every message and link the turn handed over, in the order they went out: the instant reply, each
388
+ * link, and each message written through `messageStream()` or `say()`. An item its send threw on is
389
+ * left out. Return it as the turn's answer, and an app renders it in order. Each read returns a new array.
390
+ */
391
+ readonly items: BuyToolTurnItem[];
392
+ /**
393
+ * A message stream whose messages become this turn's `text` items, after the items before them, as
394
+ * `messageStream` cuts them: push your model's text as it streams, then `end` it. Start one for each
395
+ * model message. With `onItem`, the turn spaces them by its `pace`.
396
+ */
397
+ messageStream(): MessageStream;
398
+ /** Cuts a whole text into messages, as `toMessages` does, and hands each one over as a `text` item. Resolves once every one went out. */
399
+ say(text: string): Promise<void>;
400
+ }
401
+ /** A message in the one shape the Anthropic, OpenAI and Vercel AI SDK message lists all take. */
402
+ export type BuyToolTurnMessage = {
403
+ role: 'assistant';
404
+ content: string;
405
+ } | {
406
+ role: 'user';
407
+ content: string;
408
+ };
409
+ /**
410
+ * The model's messages for a turn: the history, what Agentcard told the shopper, then the shopper's
411
+ * words. Agentcard's words join a last assistant message of plain text, a string or text parts only,
412
+ * so no provider reads two assistant messages in a row. After anything else, such as a tool result
413
+ * or an OpenAI Responses item, they come as an assistant message of their own.
414
+ */
415
+ export declare function turnMessages<M extends object>(history: readonly M[] | null | undefined, told: readonly string[], shopperText: string): Array<M | BuyToolTurnMessage>;
269
416
  /** Shared with the merchant monitor so link eligibility uses the tool's product mapping. */
270
417
  export declare function productsOf(turn: BuyTurn): BuyToolProduct[];
271
418
  /**