@agent-cards/commerce-sdk 0.18.15 → 0.18.16

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 CHANGED
@@ -886,6 +886,7 @@ The facts are `merchantName`, `totalCents`, `currency`, `cardBrand`, `cardLast4`
886
886
  | `cart_ready` | The store's checkout built the cart an answer named as still being built (`cart_building`). `text` gives the store's own total, and nothing is paid until the shopper says yes to it. |
887
887
  | `cart_needs_input` | That checkout asks the shopper something first, such as a size, a delivery address, a sign-in to the store or an approval of the most the order can cost. Their reply continues the cart. |
888
888
  | `cart_failed` | That checkout built no cart, and nothing was ordered or charged. |
889
+ | `basket_approval` | Every store in a basket reached its payment step after the answer to the shopper's yes. `text` carries the one approval link for every seller, and each store charges separately. |
889
890
 
890
891
  Agentcard can add a kind later. A kind your code does not know still carries `text`, ready to send as is.
891
892
 
@@ -0,0 +1,36 @@
1
+ /**
2
+ * A basket on a purchase answer: the carts at several sellers that one approval pays, each seller charging the card
3
+ * separately for no more than its line. `approval_url` is the one link the shopper approves on while the basket waits for
4
+ * that approval or pays under it. Null on an answer with no basket, and on an older API.
5
+ *
6
+ * A basket whose stores reach their payment step after this answer has no link yet: the link then arrives as a
7
+ * `basket_approval` purchase message.
8
+ */
9
+ export type BuyBasketStatus = 'collecting' | 'awaiting_approval' | 'approved' | 'settling' | 'done' | 'expired' | 'declined';
10
+ /**
11
+ * Where one seller's line stands: `parking` on its way to the store's payment step, `parked` there and in the approval,
12
+ * `left_out` asking its own approval, `approved`, `sent` (the card went to the store), `charged`, `needs_input` (the store
13
+ * asks more than the approval covers, and asks its own approval at the new total), `failed`, `released`.
14
+ */
15
+ export type BuyBasketLineStatus = 'parking' | 'parked' | 'left_out' | 'approved' | 'sent' | 'charged' | 'needs_input' | 'failed' | 'released';
16
+ export interface BuyBasketLine {
17
+ /** The seller as the shopper knows it: the store or the airline. */
18
+ seller: string;
19
+ status: BuyBasketLineStatus;
20
+ total_cents: number;
21
+ /** The most this seller may charge: its total_cents, since a seller is paid exactly the amount the shopper approved. */
22
+ cap_cents: number;
23
+ }
24
+ export interface BuyBasket {
25
+ id: string;
26
+ status: BuyBasketStatus;
27
+ total_cents: number;
28
+ /** The most every seller together may charge: total_cents, the sum of what each seller is paid. */
29
+ cap_cents: number;
30
+ currency: string;
31
+ approval_url: string | null;
32
+ expires_at: string;
33
+ lines: BuyBasketLine[];
34
+ }
35
+ /** The basket an answer carries, or null when it carries none, or one with a status or a line this version cannot read. */
36
+ export declare function basketOf(v: unknown): BuyBasket | null;
package/dist/basket.js ADDED
@@ -0,0 +1,22 @@
1
+ const BASKET_STATUSES = new Set(['collecting', 'awaiting_approval', 'approved', 'settling', 'done', 'expired', 'declined']);
2
+ const LINE_STATUSES = new Set(['parking', 'parked', 'left_out', 'approved', 'sent', 'charged', 'needs_input', 'failed', 'released']);
3
+ const isRecord = (v) => !!v && typeof v === 'object' && !Array.isArray(v);
4
+ const whole = (v) => typeof v === 'number' && Number.isSafeInteger(v);
5
+ /** The basket an answer carries, or null when it carries none, or one with a status or a line this version cannot read. */
6
+ export function basketOf(v) {
7
+ if (!isRecord(v) || typeof v.id !== 'string' || typeof v.status !== 'string' || !Array.isArray(v.lines))
8
+ return null;
9
+ if (!whole(v.total_cents) || !whole(v.cap_cents) || typeof v.currency !== 'string' || typeof v.expires_at !== 'string')
10
+ return null;
11
+ if (!BASKET_STATUSES.has(v.status))
12
+ return null;
13
+ const lines = [];
14
+ // Every line or none: an app never shows fewer sellers than the approval covers.
15
+ for (const l of v.lines) {
16
+ if (!isRecord(l) || typeof l.seller !== 'string' || typeof l.status !== 'string' || !LINE_STATUSES.has(l.status) || !whole(l.total_cents) || !whole(l.cap_cents))
17
+ return null;
18
+ lines.push({ seller: l.seller, status: l.status, total_cents: l.total_cents, cap_cents: l.cap_cents });
19
+ }
20
+ return { id: v.id, status: v.status, total_cents: v.total_cents, cap_cents: v.cap_cents, currency: v.currency,
21
+ approval_url: typeof v.approval_url === 'string' ? v.approval_url : null, expires_at: v.expires_at, lines };
22
+ }
@@ -33,6 +33,7 @@
33
33
  */
34
34
  import type { BuyCardSize } from './card-size.js';
35
35
  import type { BuyPlan } from './plan.js';
36
+ import type { BuyBasket } from './basket.js';
36
37
  import type { BuyMerchantStatusUpdate, BuySearchStatus } from './search-status.js';
37
38
  import { type BuyGroupsUpdate } from './search-groups.js';
38
39
  import type { BuyFreshness, BuyRecheck, BuyStoreCheckUpdate } from './client.js';
@@ -259,6 +260,8 @@ export interface BuyToolResult {
259
260
  cart_building?: BuyCartBuilding | null;
260
261
  /** The checkout plan the answer started or moved, such as a trip's flight, hotel and car with each one's state; null when none. */
261
262
  plan?: BuyPlan | null;
263
+ /** The basket the yes parked its carts into: one approval link for every seller, and each seller's line; null when none. */
264
+ basket?: BuyBasket | null;
262
265
  /** Every store the answer's searches asked and where each stands (`searchAt` narrows it to one store); null when it searched none. */
263
266
  search?: BuySearchStatus | null;
264
267
  /**
package/dist/buy-tool.js CHANGED
@@ -337,6 +337,7 @@ function answerOf(turn) {
337
337
  choice: turn.choice ?? null,
338
338
  code_request: turn.code_request ?? null,
339
339
  plan: turn.plan ?? null,
340
+ basket: turn.basket ?? null,
340
341
  search: turn.search ?? null,
341
342
  envelope: turn.raw,
342
343
  };
@@ -372,6 +373,18 @@ function onlyNextStepLinks(result, next) {
372
373
  const links = result.links.filter((link) => (link.kind === 'vault' ? next === 'add_card' : link.kind === 'approval' ? next === 'approve' : true));
373
374
  return links.length === result.links.length ? result : { ...result, links };
374
375
  }
376
+ /**
377
+ * The answer whose step is the approval it carries: an approval Agentcard already sent is the step the
378
+ * shopper takes now, so an answer that names another step names `approve` instead. The link is the one way
379
+ * to that approval, and the other step held it back and left the shopper with an address question and no
380
+ * link until the approval was about to expire (2026-11-04, an Amazon cart). An answer that names no step
381
+ * keeps naming none, as it always did.
382
+ */
383
+ function approvalIsTheStep(result) {
384
+ if (result.status !== 'approval_sent' || !result.next_action || result.next_action === 'approve')
385
+ return result;
386
+ return result.links.some((link) => link.kind === 'approval') ? { ...result, next_action: 'approve' } : result;
387
+ }
375
388
  /**
376
389
  * The answer's `messages`, read once its `reply` is final: the messages Agentcard sent while the reply
377
390
  * is still Agentcard's own, else the reply the tool wrote, each cut at its blank lines.
@@ -448,7 +461,8 @@ function askingForNamedStep(result, next) {
448
461
  }
449
462
  /** A purchase answer, read for its status. A status Agentcard adds later reads as `needs_input`. */
450
463
  function resultOfTurn(turn) {
451
- return askingForNamedStep(onlyNextStepLinks(resultOfStatus(turn), turn.next_action), turn.next_action);
464
+ const sent = approvalIsTheStep(resultOfStatus(turn));
465
+ return askingForNamedStep(onlyNextStepLinks(sent, sent.next_action), sent.next_action);
452
466
  }
453
467
  /** A purchase answer, read for its status, with every link the answer carries. */
454
468
  function resultOfStatus(turn) {
@@ -456,6 +470,12 @@ function resultOfStatus(turn) {
456
470
  const card = needsCardOf(turn, answer);
457
471
  if (card)
458
472
  return card;
473
+ // A basket awaiting its one approval: that link is the step, for every seller at once, whatever the answer's status.
474
+ const basket = turn.basket;
475
+ if (basket && basket.status === 'awaiting_approval' && basket.approval_url) {
476
+ return { ...answer, status: 'approval_sent', next_action: 'approve', approval_expires_at: basket.expires_at,
477
+ links: [...answer.links, approvalLink(basket.approval_url, basket.expires_at, null)] };
478
+ }
459
479
  switch (turn.status) {
460
480
  case 'order_placed':
461
481
  case 'partially_placed':
package/dist/client.d.ts CHANGED
@@ -10,6 +10,7 @@
10
10
  */
11
11
  import { type BuyTrip } from './trip.js';
12
12
  import { type BuyPlan } from './plan.js';
13
+ import { type BuyBasket } from './basket.js';
13
14
  import { type BuyMerchantStatusUpdate, type BuySearchStatus } from './search-status.js';
14
15
  import { type BuyGroup, type BuyGroupsUpdate } from './search-groups.js';
15
16
  import { type BuyFulfilledBy } from './fulfilled-by.js';
@@ -731,6 +732,8 @@ export interface BuyTurn {
731
732
  choice: BuyChoice | null;
732
733
  /** The checkout plan this answer's yes started or moved, with each item's state; null when none, and on an older API. */
733
734
  plan: BuyPlan | null;
735
+ /** The basket this answer's yes parked its carts into, with its one approval link and each seller's line; null when none, and on an older API. */
736
+ basket: BuyBasket | null;
734
737
  /** Every store the turn searched and where each stands; null for a turn that searched none, and on an older API. */
735
738
  search: BuySearchStatus | null;
736
739
  /** The answer as Agentcard sent it. */
package/dist/client.js CHANGED
@@ -10,6 +10,7 @@
10
10
  */
11
11
  import { tripOf } from './trip.js';
12
12
  import { planOf } from './plan.js';
13
+ import { basketOf } from './basket.js';
13
14
  import { merchantStatusOf, searchOf } from './search-status.js';
14
15
  import { groupListOf, groupOf, groupsOf } from './search-groups.js';
15
16
  import { isRecord } from './verify.js';
@@ -405,6 +406,7 @@ export function turnOf(body) {
405
406
  cart_building: cartBuildingOf(body.cart_building),
406
407
  choice: choiceOf(body.choice),
407
408
  plan: planOf(body.plan),
409
+ basket: basketOf(body.basket),
408
410
  search: searchOf(body.search),
409
411
  raw: body,
410
412
  };
package/dist/index.d.ts CHANGED
@@ -23,6 +23,7 @@ export { turnStream, readTurn, TURN_RECORD_TTL_SECONDS, type TurnStream, type Tu
23
23
  export { TURN_RECOVERY_ON, isTurnId, type TurnEvent, type TurnRecord, type TurnRecordStore } from './turn-record.js';
24
24
  export type { BuyLateResults, BuyStoreWait } from './store-wait.js';
25
25
  export { planOf, type BuyPlan, type BuyPlanItem, type BuyPlanItemState, type BuyPlanStatus } from './plan.js';
26
+ export { basketOf, type BuyBasket, type BuyBasketLine, type BuyBasketLineStatus, type BuyBasketStatus } from './basket.js';
26
27
  export { tripOf, type BuyTrip, type TripLane, type TripLaneName, type TripLaneResult, type TripLaneStatus } from './trip.js';
27
28
  export { fulfilledByLine, fulfilledByOf, type BuyFulfilledBy, type BuyFulfilledByKind } from './fulfilled-by.js';
28
29
  export { cardSizeOf, type BuyCardSize } from './card-size.js';
package/dist/index.js CHANGED
@@ -22,6 +22,7 @@ export { AGENTCARD_EVENT_TYPES, } from './events.js';
22
22
  export { turnStream, readTurn, TURN_RECORD_TTL_SECONDS } from './turn-stream.js';
23
23
  export { TURN_RECOVERY_ON, isTurnId } from './turn-record.js';
24
24
  export { planOf } from './plan.js';
25
+ export { basketOf } from './basket.js';
25
26
  export { tripOf } from './trip.js';
26
27
  export { fulfilledByLine, fulfilledByOf } from './fulfilled-by.js';
27
28
  export { cardSizeOf } from './card-size.js';
package/dist/message.d.ts CHANGED
@@ -25,8 +25,9 @@ import { type AgentcardEvent } from './verify.js';
25
25
  * - `cart_ready`: the store's checkout built the cart an answer named as still being built (`cart_building`), at the store's own total. Nothing is paid until the shopper says yes to it.
26
26
  * - `cart_needs_input`: that checkout asks the shopper something first, such as a size, a delivery address, a sign-in to the store or an approval of the most the order can cost.
27
27
  * - `cart_failed`: that checkout built no cart, and nothing was ordered or charged.
28
+ * - `basket_approval`: every store in a basket reached its payment step after the answer to the shopper's yes; `text` carries the one approval link for every seller, and each store charges separately.
28
29
  */
29
- export type PurchaseMessageKind = 'order_placed' | 'order_failed' | 'order_not_placed_yet' | 'order_unconfirmed' | 'order_placing' | 'approval_expiring' | 'approval_expired' | 'approval_declined' | 'payment_declined' | 'card_link_unfinished' | 'card_link_expired' | 'cart_idle' | 'card_added' | 'store_connected' | 'store_connect_failed' | 'store_connect_expired' | 'cart_ready' | 'cart_needs_input' | 'cart_failed';
30
+ export type PurchaseMessageKind = 'order_placed' | 'order_failed' | 'order_not_placed_yet' | 'order_unconfirmed' | 'order_placing' | 'approval_expiring' | 'approval_expired' | 'approval_declined' | 'payment_declined' | 'card_link_unfinished' | 'card_link_expired' | 'cart_idle' | 'card_added' | 'store_connected' | 'store_connect_failed' | 'store_connect_expired' | 'cart_ready' | 'cart_needs_input' | 'cart_failed' | 'basket_approval';
30
31
  /** The message's values one by one, for a company that writes its own copy. A fact that does not apply is absent. */
31
32
  export interface PurchaseFacts {
32
33
  merchantName?: string;
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@agent-cards/commerce-sdk",
3
- "version": "0.18.15",
3
+ "version": "0.18.16",
4
4
  "description": "Give your agent one tool that buys for your shoppers at real stores, and post each purchase update Agentcard sends into the shopper's chat, over a connection your server opens or a webhook endpoint.",
5
5
  "homepage": "https://opencommerce.sh",
6
6
  "type": "module",