@agent-cards/commerce-sdk 0.15.0 → 0.16.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 +52 -5
- package/dist/ack.d.ts +26 -16
- package/dist/ack.js +64 -2
- package/dist/browser.d.ts +1 -0
- package/dist/browser.js +2 -0
- package/dist/buy-tool.d.ts +45 -8
- package/dist/buy-tool.js +228 -27
- package/dist/client.d.ts +30 -0
- package/dist/client.js +11 -0
- package/dist/definition.js +2 -0
- package/dist/direct.d.ts +5 -2
- package/dist/index.d.ts +4 -2
- package/dist/index.js +3 -1
- package/dist/messages.js +4 -4
- package/dist/model-view.d.ts +48 -4
- package/dist/model-view.js +208 -27
- package/dist/order.d.ts +119 -0
- package/dist/order.js +155 -0
- package/dist/sentences.d.ts +3 -0
- package/dist/sentences.js +29 -0
- package/dist/shopper-message.d.ts +10 -0
- package/dist/shopper-message.js +51 -11
- package/dist/texting.d.ts +81 -0
- package/dist/texting.js +347 -0
- package/package.json +1 -1
package/dist/order.js
ADDED
|
@@ -0,0 +1,155 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Where a shopper's order is, read from the messages Agentcard sends about it, for an app that draws
|
|
3
|
+
* an order tracker instead of a line of text. `readShopperMessage` puts it on `message.order`;
|
|
4
|
+
* `updateOrder` folds the next message about the same order into the one a tracker shows, and
|
|
5
|
+
* `arrivalText` writes the arrival line. No import reaches outside the package, so the browser entry
|
|
6
|
+
* carries this file too.
|
|
7
|
+
*/
|
|
8
|
+
/** The steps a tracker draws, left to right. An order that ends early reads `canceled` or `failed`. */
|
|
9
|
+
export const ORDER_STEPS = ['placed', 'confirmed', 'preparing', 'on_the_way', 'delivered'];
|
|
10
|
+
const LABELS = {
|
|
11
|
+
placed: 'Placed',
|
|
12
|
+
confirmed: 'Accepted',
|
|
13
|
+
preparing: 'Preparing',
|
|
14
|
+
on_the_way: 'On the way',
|
|
15
|
+
delivered: 'Delivered',
|
|
16
|
+
canceled: 'Canceled',
|
|
17
|
+
failed: 'Failed',
|
|
18
|
+
};
|
|
19
|
+
/**
|
|
20
|
+
* The step each delivery stage of `order.updated` reads as. The store accepting the order is
|
|
21
|
+
* `confirmed`; a driver being found, waiting at the store or the order being ready is `preparing`;
|
|
22
|
+
* the order picked up or nearby is `on_the_way`.
|
|
23
|
+
*/
|
|
24
|
+
const STAGE_STEPS = {
|
|
25
|
+
placed: 'placed',
|
|
26
|
+
confirmed: 'confirmed',
|
|
27
|
+
courier_assigned: 'preparing',
|
|
28
|
+
courier_at_store: 'preparing',
|
|
29
|
+
ready_for_pickup: 'preparing',
|
|
30
|
+
picked_up: 'on_the_way',
|
|
31
|
+
arriving: 'on_the_way',
|
|
32
|
+
delivered: 'delivered',
|
|
33
|
+
canceled: 'canceled',
|
|
34
|
+
};
|
|
35
|
+
/** The step an `order.updated` stage reads as, or null for a stage the package does not know. */
|
|
36
|
+
export function orderStepOf(stage) {
|
|
37
|
+
return (stage && STAGE_STEPS[stage]) || null;
|
|
38
|
+
}
|
|
39
|
+
const RANK = { placed: 0, confirmed: 1, preparing: 2, on_the_way: 3, delivered: 4, canceled: 5, failed: 5 };
|
|
40
|
+
/**
|
|
41
|
+
* The tracker's steps. An order at a step has passed every step before it. A canceled order has
|
|
42
|
+
* passed the step it was canceled at, `reached`, and a failed one passed none.
|
|
43
|
+
*/
|
|
44
|
+
function stepsOf(step, reached) {
|
|
45
|
+
const ended = step === 'canceled' || step === 'failed';
|
|
46
|
+
const at = ended ? (reached && reached !== 'canceled' && reached !== 'failed' ? RANK[reached] : step === 'canceled' ? 0 : -1) : RANK[step];
|
|
47
|
+
return ORDER_STEPS.map((s) => ({
|
|
48
|
+
step: s,
|
|
49
|
+
label: LABELS[s],
|
|
50
|
+
state: RANK[s] < at || (ended && RANK[s] === at) ? 'done' : RANK[s] === at ? 'current' : 'upcoming',
|
|
51
|
+
}));
|
|
52
|
+
}
|
|
53
|
+
/** The order a message describes, from the values it names. */
|
|
54
|
+
export function orderFrom(fields) {
|
|
55
|
+
const earliest = fields.arrivalEarliest ?? null;
|
|
56
|
+
const latest = fields.arrivalLatest ?? null;
|
|
57
|
+
return {
|
|
58
|
+
orderId: fields.orderId ?? null,
|
|
59
|
+
merchantName: fields.merchantName ?? null,
|
|
60
|
+
storeName: fields.storeName ?? null,
|
|
61
|
+
step: fields.step,
|
|
62
|
+
label: LABELS[fields.step],
|
|
63
|
+
steps: stepsOf(fields.step, fields.reached ?? null),
|
|
64
|
+
title: fields.title ?? null,
|
|
65
|
+
detail: fields.detail ?? null,
|
|
66
|
+
arrival: earliest || latest ? { earliest, latest } : null,
|
|
67
|
+
deliveryWindow: fields.deliveryWindow ?? null,
|
|
68
|
+
totalCents: fields.totalCents ?? null,
|
|
69
|
+
currency: fields.currency ?? null,
|
|
70
|
+
retailerOrderId: fields.retailerOrderId ?? null,
|
|
71
|
+
deliveredAt: fields.deliveredAt ?? null,
|
|
72
|
+
reason: fields.reason ?? null,
|
|
73
|
+
updatedAt: fields.updatedAt,
|
|
74
|
+
};
|
|
75
|
+
}
|
|
76
|
+
/** Values that stay true of an order whatever step it reaches. */
|
|
77
|
+
const LASTING = ['orderId', 'merchantName', 'storeName', 'totalCents', 'currency', 'retailerOrderId', 'deliveryWindow'];
|
|
78
|
+
/**
|
|
79
|
+
* The order a tracker shows once `next` arrives, for two messages about the same order. The order
|
|
80
|
+
* never moves back a step: Agentcard delivers each message at least once and in no set order, so a
|
|
81
|
+
* late `order.confirmed` after the driver picked the order up leaves the tracker at `on_the_way`. At
|
|
82
|
+
* the same step, the message with the later `updatedAt` wins. A value the shown message lacks, such
|
|
83
|
+
* as the total `order_placed` named or the restaurant `order.updated` named, comes from the other,
|
|
84
|
+
* and so does the store's line when both are at the same step. An arrival estimate holds until the
|
|
85
|
+
* order is delivered, canceled or failed.
|
|
86
|
+
*
|
|
87
|
+
* ```ts
|
|
88
|
+
* trackers.set(order.orderId, updateOrder(trackers.get(order.orderId), order));
|
|
89
|
+
* ```
|
|
90
|
+
*/
|
|
91
|
+
export function updateOrder(previous, next) {
|
|
92
|
+
if (!previous)
|
|
93
|
+
return next;
|
|
94
|
+
const diff = RANK[next.step] - RANK[previous.step];
|
|
95
|
+
const newer = diff !== 0 ? diff > 0 : !(Date.parse(next.updatedAt) < Date.parse(previous.updatedAt));
|
|
96
|
+
const [shown, other] = newer ? [next, previous] : [previous, next];
|
|
97
|
+
const merged = { ...shown };
|
|
98
|
+
for (const key of LASTING)
|
|
99
|
+
if (merged[key] === null)
|
|
100
|
+
merged[key] = other[key];
|
|
101
|
+
if (shown.step === other.step) {
|
|
102
|
+
merged.title ??= other.title;
|
|
103
|
+
merged.detail ??= other.detail;
|
|
104
|
+
}
|
|
105
|
+
// An arrival estimate holds until the order arrives, whichever step quoted it.
|
|
106
|
+
if (shown.step !== 'delivered' && shown.step !== 'canceled' && shown.step !== 'failed')
|
|
107
|
+
merged.arrival ??= other.arrival;
|
|
108
|
+
return merged;
|
|
109
|
+
}
|
|
110
|
+
function validDate(value) {
|
|
111
|
+
if (!value)
|
|
112
|
+
return null;
|
|
113
|
+
const date = new Date(value);
|
|
114
|
+
return Number.isNaN(date.getTime()) ? null : date;
|
|
115
|
+
}
|
|
116
|
+
/**
|
|
117
|
+
* The line a tracker writes under the steps about when the order arrives, in the shopper's time zone,
|
|
118
|
+
* or null when Agentcard sent no time for it:
|
|
119
|
+
*
|
|
120
|
+
* - `Arriving 12:04 – 12:22 PM` from the store's estimate, with the weekday when it is not today;
|
|
121
|
+
* - `Arriving by 12:22 PM` when the estimate names only its latest time;
|
|
122
|
+
* - `Expected Sat, Sep 6 - Tue, Sep 9` from a retailer's delivery window;
|
|
123
|
+
* - `Delivered at 12:20 PM` once delivered.
|
|
124
|
+
*
|
|
125
|
+
* A canceled or failed order has no arrival line.
|
|
126
|
+
*/
|
|
127
|
+
export function arrivalText(order, options = {}) {
|
|
128
|
+
const locale = options.locale ?? 'en-US';
|
|
129
|
+
const now = new Date(options.now ?? Date.now());
|
|
130
|
+
const time = (date) => {
|
|
131
|
+
const day = (d) => new Intl.DateTimeFormat(locale, { timeZone: options.timeZone, year: 'numeric', month: 'numeric', day: 'numeric' }).format(d);
|
|
132
|
+
const today = day(date) === day(now);
|
|
133
|
+
return new Intl.DateTimeFormat(locale, { timeZone: options.timeZone, hour: 'numeric', minute: '2-digit', ...(today ? {} : { weekday: 'short' }) });
|
|
134
|
+
};
|
|
135
|
+
if (order.step === 'canceled' || order.step === 'failed')
|
|
136
|
+
return null;
|
|
137
|
+
if (order.step === 'delivered') {
|
|
138
|
+
const at = validDate(order.deliveredAt);
|
|
139
|
+
return at ? `Delivered at ${time(at).format(at)}` : 'Delivered';
|
|
140
|
+
}
|
|
141
|
+
const earliest = validDate(order.arrival?.earliest);
|
|
142
|
+
const latest = validDate(order.arrival?.latest);
|
|
143
|
+
if (earliest && latest && earliest.getTime() < latest.getTime()) {
|
|
144
|
+
const format = time(earliest);
|
|
145
|
+
const range = typeof format.formatRange === 'function' ? format.formatRange(earliest, latest) : `${format.format(earliest)} – ${format.format(latest)}`;
|
|
146
|
+
return `Arriving ${range}`;
|
|
147
|
+
}
|
|
148
|
+
if (latest)
|
|
149
|
+
return `Arriving by ${time(latest).format(latest)}`;
|
|
150
|
+
if (earliest)
|
|
151
|
+
return `Arriving around ${time(earliest).format(earliest)}`;
|
|
152
|
+
if (order.deliveryWindow)
|
|
153
|
+
return `Expected ${order.deliveryWindow}`;
|
|
154
|
+
return null;
|
|
155
|
+
}
|
|
@@ -0,0 +1,29 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Where one sentence of Agentcard's words ends and the next begins: the space after a full stop, a question mark or an
|
|
3
|
+
* exclamation mark. A stop that ends a short form or an initial ends no sentence, so `Mr. Coffee`, `Dr. Pepper`,
|
|
4
|
+
* `St. Louis`, `J. Crew`, `U.S. Polo`, `e.g. a mug`, `Acme Inc. mugs` and `No. 7` each stay in one sentence. A price
|
|
5
|
+
* (`$24.99`), a size (`8.5`) and a domain (`target.com`) carry no space after their stop, so they end none either.
|
|
6
|
+
*
|
|
7
|
+
* On 2026-10-27 a reply that led into its list with `target has two colors of the Mr. Coffee Retro Brew French Press:`
|
|
8
|
+
* reached the shopper as `target has two colors of the Mr.`: the stop after `Mr` was read as a sentence's end.
|
|
9
|
+
*/
|
|
10
|
+
const SHORT_FORMS = ['mr', 'mrs', 'ms', 'dr', 'st', 'jr', 'sr', 'mt', 'ft', 'vs', 'inc', 'co', 'ltd', 'e.g', 'i.e'];
|
|
11
|
+
const anyCase = (word) => word.replace(/[a-z]/g, (letter) => `[${letter}${letter.toUpperCase()}]`).replace(/\./g, '\\.');
|
|
12
|
+
/** What comes before a letter that is a size, not an initial: `size `, `sizes `, `in `, `and `, `or `, or another size and a comma. */
|
|
13
|
+
const NOT_A_SIZE = '(?<!(?:\\b(?:[Ss]izes?|in|and|or)|\\b[A-Z]{1,3},)\\s)';
|
|
14
|
+
export const SENTENCE_END = new RegExp([
|
|
15
|
+
'(?<=[.!?])',
|
|
16
|
+
`(?<!(?:^|[^\\p{L}.])(?:${SHORT_FORMS.map(anyCase).join('|')})\\.)`,
|
|
17
|
+
// An initial, or initials such as `U.S.`, written as capitals. A size, `size S.` or `in S, M and L.`, is no initial.
|
|
18
|
+
`(?<!(?:^|[^\\p{L}])${NOT_A_SIZE}(?:\\p{L}\\.)*\\p{Lu}\\.)`,
|
|
19
|
+
// `No.` stands for a number before a digit; anywhere else it is the answer "no".
|
|
20
|
+
'(?!(?<=(?:^|[^\\p{L}])[Nn][Oo]\\.)\\s+\\d)',
|
|
21
|
+
'\\s+',
|
|
22
|
+
].join(''), 'u');
|
|
23
|
+
/** Where the last sentence of `text` begins: 0 when the text is one sentence. */
|
|
24
|
+
export function lastSentenceStart(text) {
|
|
25
|
+
let start = 0;
|
|
26
|
+
for (const end of text.matchAll(new RegExp(SENTENCE_END.source, 'gu')))
|
|
27
|
+
start = end.index + end[0].length;
|
|
28
|
+
return start;
|
|
29
|
+
}
|
|
@@ -5,6 +5,7 @@
|
|
|
5
5
|
*/
|
|
6
6
|
import { type AgentcardEvent } from './verify.js';
|
|
7
7
|
import { type PurchaseFacts, type PurchaseMessageKind } from './message.js';
|
|
8
|
+
import { type ShopperOrder } from './order.js';
|
|
8
9
|
/**
|
|
9
10
|
* The event types that carry a message for a shopper. Subscribe your endpoint to each by name:
|
|
10
11
|
* a catch-all `*` alone does not turn `purchase.message` on.
|
|
@@ -36,6 +37,8 @@ export type ShopperMessageKind = PurchaseMessageKind | 'order_confirmed' | 'orde
|
|
|
36
37
|
export interface ShopperFacts extends PurchaseFacts {
|
|
37
38
|
/** The retailer's own order number, as on its confirmation and account page. */
|
|
38
39
|
retailerOrderId?: string;
|
|
40
|
+
/** The place the order comes from, such as the restaurant on DoorDash, when Agentcard names it. */
|
|
41
|
+
storeName?: string;
|
|
39
42
|
/** The retailer's quoted delivery window, in its own words. */
|
|
40
43
|
deliveryWindow?: string;
|
|
41
44
|
/** The order's delivery stage. */
|
|
@@ -74,6 +77,13 @@ export interface ShopperMessage {
|
|
|
74
77
|
/** For your agent: the facts of what happened, marked as written by the system and not typed by the shopper. */
|
|
75
78
|
note: string;
|
|
76
79
|
facts: ShopperFacts;
|
|
80
|
+
/**
|
|
81
|
+
* Where the order is, for a message about an order a tracker can show: `order_placed`,
|
|
82
|
+
* `order_failed`, `order_confirmed`, and `order_updated` at a stage the package knows. Null for
|
|
83
|
+
* every other message. Keep one tracker per `order.orderId` and fold each new one in with
|
|
84
|
+
* `updateOrder`, so successive messages move one tracker instead of adding lines.
|
|
85
|
+
*/
|
|
86
|
+
order: ShopperOrder | null;
|
|
77
87
|
/**
|
|
78
88
|
* True when the message ends a step the shopper took outside the chat, one of `NEXT_STEP_KINDS`:
|
|
79
89
|
* open `buy.nextStep(message)` and let your model take the next step, such as the card ask or the
|
package/dist/shopper-message.js
CHANGED
|
@@ -5,6 +5,7 @@
|
|
|
5
5
|
*/
|
|
6
6
|
import { isRecord } from './verify.js';
|
|
7
7
|
import { readPurchaseMessage } from './message.js';
|
|
8
|
+
import { orderFrom, orderStepOf } from './order.js';
|
|
8
9
|
/**
|
|
9
10
|
* The event types that carry a message for a shopper. Subscribe your endpoint to each by name:
|
|
10
11
|
* a catch-all `*` alone does not turn `purchase.message` on.
|
|
@@ -30,6 +31,8 @@ function text(value, max = 200) {
|
|
|
30
31
|
function id(value) {
|
|
31
32
|
return typeof value === 'string' && value !== '' ? value : undefined;
|
|
32
33
|
}
|
|
34
|
+
/** Unix seconds as an ISO 8601 timestamp. */
|
|
35
|
+
const isoOf = (created) => new Date(created * 1000).toISOString();
|
|
33
36
|
function count(value) {
|
|
34
37
|
return typeof value === 'number' && Number.isFinite(value) ? value : undefined;
|
|
35
38
|
}
|
|
@@ -101,12 +104,18 @@ function readOrderConfirmed(event) {
|
|
|
101
104
|
const deliveryWindow = text(data.delivery_window, 80);
|
|
102
105
|
const facts = present({
|
|
103
106
|
merchantName: store ?? undefined,
|
|
107
|
+
storeName: text(data.store_name, 64),
|
|
104
108
|
totalCents: count(data.final_total_cents) ?? count(data.charged_cents),
|
|
105
109
|
currency: text(data.currency, 8),
|
|
106
110
|
orderId: id(data.order_id),
|
|
107
111
|
retailerOrderId,
|
|
108
112
|
deliveryWindow,
|
|
109
113
|
});
|
|
114
|
+
const order = orderFrom({
|
|
115
|
+
...facts,
|
|
116
|
+
step: 'confirmed',
|
|
117
|
+
updatedAt: text(data.confirmed_at, 40) ?? isoOf(event.created),
|
|
118
|
+
});
|
|
110
119
|
const who = store ? `${store} confirmed your order` : 'The store confirmed your order';
|
|
111
120
|
return {
|
|
112
121
|
...base,
|
|
@@ -117,6 +126,7 @@ function readOrderConfirmed(event) {
|
|
|
117
126
|
`${retailerOrderId ? ` The store's order number is ${retailerOrderId}.` : ''}` +
|
|
118
127
|
`${deliveryWindow ? ` The store quotes delivery ${deliveryWindow}.` : ''} ${SYSTEM_LINE}]`,
|
|
119
128
|
facts,
|
|
129
|
+
order,
|
|
120
130
|
};
|
|
121
131
|
}
|
|
122
132
|
function readOrderUpdated(event) {
|
|
@@ -134,6 +144,30 @@ function readOrderUpdated(event) {
|
|
|
134
144
|
return null;
|
|
135
145
|
const arrival = isRecord(data.estimated_arrival) ? data.estimated_arrival : {};
|
|
136
146
|
const previous = text(data.previous_status, 40);
|
|
147
|
+
const facts = present({
|
|
148
|
+
merchantName: store ?? undefined,
|
|
149
|
+
storeName: text(data.store_name, 64),
|
|
150
|
+
orderId: id(data.order_id),
|
|
151
|
+
orderStatus: status,
|
|
152
|
+
previousOrderStatus: previous,
|
|
153
|
+
statusTitle,
|
|
154
|
+
arrivalEarliest: text(arrival.earliest, 40),
|
|
155
|
+
arrivalLatest: text(arrival.latest, 40),
|
|
156
|
+
deliveredAt: text(data.delivered_at, 40),
|
|
157
|
+
canceledReason,
|
|
158
|
+
});
|
|
159
|
+
const step = orderStepOf(status);
|
|
160
|
+
const order = step
|
|
161
|
+
? orderFrom({
|
|
162
|
+
...facts,
|
|
163
|
+
step,
|
|
164
|
+
reached: orderStepOf(previous),
|
|
165
|
+
title: statusTitle,
|
|
166
|
+
detail: text(data.status_detail, 200),
|
|
167
|
+
reason: canceledReason,
|
|
168
|
+
updatedAt: text(data.updated_at, 40) ?? isoOf(event.created),
|
|
169
|
+
})
|
|
170
|
+
: null;
|
|
137
171
|
return {
|
|
138
172
|
...base,
|
|
139
173
|
kind: 'order_updated',
|
|
@@ -142,17 +176,8 @@ function readOrderUpdated(event) {
|
|
|
142
176
|
note: `[event: this user's order${store ? ` at ${store}` : ''} moved to the stage ${status}` +
|
|
143
177
|
`${previous ? ` from ${previous}` : ''}, outside this conversation.` +
|
|
144
178
|
`${statusTitle ? ` The store says: ${statusTitle}.` : ''}${canceledReason ? ` The store's reason: ${canceledReason}.` : ''} ${SYSTEM_LINE}]`,
|
|
145
|
-
facts
|
|
146
|
-
|
|
147
|
-
orderId: id(data.order_id),
|
|
148
|
-
orderStatus: status,
|
|
149
|
-
previousOrderStatus: previous,
|
|
150
|
-
statusTitle,
|
|
151
|
-
arrivalEarliest: text(arrival.earliest, 40),
|
|
152
|
-
arrivalLatest: text(arrival.latest, 40),
|
|
153
|
-
deliveredAt: text(data.delivered_at, 40),
|
|
154
|
-
canceledReason,
|
|
155
|
-
}),
|
|
179
|
+
facts,
|
|
180
|
+
order,
|
|
156
181
|
};
|
|
157
182
|
}
|
|
158
183
|
/**
|
|
@@ -174,8 +199,23 @@ export function readShopperMessage(event) {
|
|
|
174
199
|
type: 'purchase.message',
|
|
175
200
|
conversationId: id(event.data.conversation_id) ?? null,
|
|
176
201
|
advances: NEXT_STEP.has(message.kind),
|
|
202
|
+
order: purchaseOrder(message.kind, message.facts, event.created),
|
|
177
203
|
};
|
|
178
204
|
}
|
|
205
|
+
/** The order an `order_placed` or `order_failed` message describes; null for every other kind. */
|
|
206
|
+
function purchaseOrder(kind, facts, created) {
|
|
207
|
+
if (kind !== 'order_placed' && kind !== 'order_failed')
|
|
208
|
+
return null;
|
|
209
|
+
return orderFrom({
|
|
210
|
+
orderId: facts.orderId,
|
|
211
|
+
merchantName: facts.merchantName,
|
|
212
|
+
totalCents: facts.totalCents,
|
|
213
|
+
currency: facts.currency,
|
|
214
|
+
step: kind === 'order_placed' ? 'placed' : 'failed',
|
|
215
|
+
reason: kind === 'order_failed' ? facts.reason : undefined,
|
|
216
|
+
updatedAt: isoOf(created),
|
|
217
|
+
});
|
|
218
|
+
}
|
|
179
219
|
/**
|
|
180
220
|
* Whether the message stopped holding before `now` (unix milliseconds, default the system clock):
|
|
181
221
|
* the link or the window its text names has closed. Drop a stale message instead of sending it.
|
|
@@ -0,0 +1,81 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Agentcard's words as a chat shows them: every list as bullet lines that start with `• `, and, for an app that asks,
|
|
3
|
+
* the texting style a chat writes in. The reply a turn hands out as written (`replyAsWritten`) and each message
|
|
4
|
+
* `onText` hears come through here, so an app shows them with no formatting of its own.
|
|
5
|
+
*/
|
|
6
|
+
import type { BuyToolResult } from './buy-tool.js';
|
|
7
|
+
import type { BuyChoice } from './client.js';
|
|
8
|
+
/**
|
|
9
|
+
* The text with every list written as bullet lines that start with `• `: lines marked `- `, `* `, `• `, `1. ` or
|
|
10
|
+
* `1) `, and two or more short lines in a row that follow a line ending in `:` or `?`, such as the choices of bread
|
|
11
|
+
* after `which bread do you want?`. A list's lead-in line stays, and so does prose. One line on its own is no list.
|
|
12
|
+
*
|
|
13
|
+
* ```ts
|
|
14
|
+
* asBullets('which bread do you want?\n\nSourdough\nWhole wheat'); // 'which bread do you want?\n\n• Sourdough\n• Whole wheat'
|
|
15
|
+
* ```
|
|
16
|
+
*/
|
|
17
|
+
export declare function asBullets(text: string): string;
|
|
18
|
+
/**
|
|
19
|
+
* The text in a chat's texting style: every list as `• ` bullet lines (`asBullets`), no other markdown (headings, bold,
|
|
20
|
+
* a lone `- ` or `* ` marker), no spaced dashes, which become commas, and lowercase at the start of each sentence and
|
|
21
|
+
* for "I". A capital inside a sentence is a name and stays, and so does an all-caps word such as "USB". A list line
|
|
22
|
+
* keeps its first word as written, since it is usually a product's or a store's name.
|
|
23
|
+
*
|
|
24
|
+
* ```ts
|
|
25
|
+
* textingStyle('Here are two — both ship today. Which one?\n- Anker cable\n- Onn cable');
|
|
26
|
+
* // 'here are two, both ship today. which one?\n• Anker cable\n• Onn cable'
|
|
27
|
+
* ```
|
|
28
|
+
*/
|
|
29
|
+
export declare function textingStyle(text: string): string;
|
|
30
|
+
/** Options for `replyAsWritten`. */
|
|
31
|
+
export interface ReplyAsWrittenOptions {
|
|
32
|
+
/** `true` writes the reply in the texting style (`textingStyle`): lowercase sentence starts, no markdown, no spaced dashes, `• ` bullets kept. */
|
|
33
|
+
textingStyle?: boolean;
|
|
34
|
+
}
|
|
35
|
+
/**
|
|
36
|
+
* Agentcard's reply, ready for the shopper as written, when the answer only asks them something: a `needs_input`
|
|
37
|
+
* answer with a reply, such as a size, a bread or a store to pick, or a cart's total and the ask for their yes. The
|
|
38
|
+
* reply comes with every link taken out, as the model reads it, without the list of products or stores whose cards
|
|
39
|
+
* went out (`withoutCardList`), and with every list left in it as `• ` bullet lines (`asBullets`). A numbered list
|
|
40
|
+
* that names the answer's products or stores keeps its numbers, because the shopper picks from it by number ("number
|
|
41
|
+
* 6"), and Agentcard and `directCall` read that number against the list. Null for any other answer, which your model
|
|
42
|
+
* puts in its own words, for a line the tool wrote itself for your model, and once the shopper read the reply through
|
|
43
|
+
* `onText` (`shopper_already_read_reply`). `{ textingStyle: true }` hands it over in the texting style (`textingStyle`).
|
|
44
|
+
*
|
|
45
|
+
* ```ts
|
|
46
|
+
* replyAsWritten(result); // 'which bread do you want?\n\n• Sourdough\n• Whole wheat'
|
|
47
|
+
* ```
|
|
48
|
+
*/
|
|
49
|
+
export declare function replyAsWritten(result: BuyToolResult, options?: ReplyAsWrittenOptions): string | null;
|
|
50
|
+
/**
|
|
51
|
+
* One message of Agentcard's, as `onText` hands it to the app: its lists as `• ` bullet lines and, with `texting`, in
|
|
52
|
+
* the texting style. A numbered list keeps its numbers, since a streamed message still lists the products its cards
|
|
53
|
+
* show and the shopper may pick one by its number. `leadIn` says the message before it ended in a colon or a question
|
|
54
|
+
* mark, so the choices that open this one are that question's list.
|
|
55
|
+
*/
|
|
56
|
+
export declare function messageForShopper(text: string, leadIn: boolean, texting: boolean): string;
|
|
57
|
+
/** True when a message ends in a colon or a question mark, so a list that opens the next one belongs to it. */
|
|
58
|
+
export declare function leadsIntoList(text: string): boolean;
|
|
59
|
+
/**
|
|
60
|
+
* A choice's values as the shopper reads them, one `• ` line each with its extra price after it: `• Gluten-free bread
|
|
61
|
+
* (+$2.46)`. A choice of more than one group leads each group's lines with its name, as `Size:`.
|
|
62
|
+
*
|
|
63
|
+
* ```ts
|
|
64
|
+
* choiceLines(result.choice!); // '• Ciabatta\n• Sourdough\n• Gluten-free bread (+$2.46)'
|
|
65
|
+
* ```
|
|
66
|
+
*/
|
|
67
|
+
export declare function choiceLines(choice: BuyChoice): string;
|
|
68
|
+
/**
|
|
69
|
+
* The text with the choice's values listed one per line (choiceLines) under the line that asks the question, when the text
|
|
70
|
+
* does not list them already: Agentcard's reply may name them inside a sentence, as "i can also do sourdough, whole wheat,
|
|
71
|
+
* or gluten-free bread for $2.46 more" did. A bracket that holds only the values goes, since the list replaces it. A text
|
|
72
|
+
* that asks nothing gets the list at its end. When every value already opens a line, a priced one that leaves its price out
|
|
73
|
+
* gets it at the end of its line, and the text is otherwise as it is, as it is with no choice.
|
|
74
|
+
*
|
|
75
|
+
* ```ts
|
|
76
|
+
* withChoice('which bread do you want?', result.choice); // 'which bread do you want?\n• Ciabatta\n• Sourdough'
|
|
77
|
+
* ```
|
|
78
|
+
*/
|
|
79
|
+
export declare function withChoice(text: string, choice: BuyChoice | null): string;
|
|
80
|
+
/** True when the shopper's messages already list every value of the choice one per line, each priced one with its extra price. */
|
|
81
|
+
export declare function messagesListChoice(messages: readonly string[], choice: BuyChoice): boolean;
|