@advertising-chat/sdk 0.2.1 → 0.17.3
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 +176 -8
- package/bin/client.d.ts +122 -14
- package/bin/client.js +207 -31
- package/bin/client.js.map +1 -1
- package/bin/index.d.ts +3 -2
- package/bin/index.js +8 -1
- package/bin/index.js.map +1 -1
- package/bin/render/llm.d.ts +48 -0
- package/bin/render/llm.js +57 -0
- package/bin/render/llm.js.map +1 -0
- package/bin/render/webchat.d.ts +5 -0
- package/bin/render/webchat.js +3 -0
- package/bin/render/webchat.js.map +1 -1
- package/bin/render/whatsapp.d.ts +32 -2
- package/bin/render/whatsapp.js +70 -9
- package/bin/render/whatsapp.js.map +1 -1
- package/bin/types.d.ts +105 -3
- package/bin/types.js +10 -1
- package/bin/types.js.map +1 -1
- package/package.json +1 -1
package/README.md
CHANGED
|
@@ -8,14 +8,17 @@ channel. Zero runtime dependencies (uses native `fetch`, Node >= 18).
|
|
|
8
8
|
npm install @advertising-chat/sdk
|
|
9
9
|
```
|
|
10
10
|
|
|
11
|
+
Basic serving is two steps: **fetch an ad, render it with a "Sponsored"
|
|
12
|
+
label.** You don't manage ad units, and you don't wire up tracking —
|
|
13
|
+
impressions and clicks are attributed server-side via your fetch key.
|
|
14
|
+
|
|
11
15
|
```ts
|
|
12
|
-
import { AdChat, renderWebchat,
|
|
16
|
+
import { AdChat, renderWebchat, UniversalAdPayload } from '@advertising-chat/sdk';
|
|
13
17
|
|
|
14
|
-
const adchat = new AdChat({
|
|
18
|
+
const adchat = new AdChat({ fetchKey: process.env.ADCHAT_FETCH_KEY! });
|
|
15
19
|
|
|
16
20
|
// Fetch an ad, passing conversation context for relevance.
|
|
17
21
|
const ad = await adchat.fetchAd<UniversalAdPayload>({
|
|
18
|
-
adUnit: 'my-ad-unit',
|
|
19
22
|
subscriber: sessionId, // anonymous id — enables frequency capping
|
|
20
23
|
context: [
|
|
21
24
|
{ role: 'user', text: 'Best hotels in Rome?' },
|
|
@@ -26,16 +29,181 @@ const ad = await adchat.fetchAd<UniversalAdPayload>({
|
|
|
26
29
|
if (ad) {
|
|
27
30
|
// null means no-fill: continue without an ad.
|
|
28
31
|
const msg = renderWebchat(ad.payload); // { text, imageUrl, buttons, disclosure }
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
// Button URLs are click-tracking URLs (302 to the advertiser) — use verbatim.
|
|
32
|
-
await adchat.recordEvent({ messageId: ad.messageId, type: 'send' });
|
|
33
|
-
await adchat.recordConversion({ messageId: ad.messageId, name: 'purchase', value: 19.99, currency: 'USD' });
|
|
32
|
+
// Render msg with a "Sponsored" label. Button URLs are already
|
|
33
|
+
// click-tracking URLs (302 to the advertiser) — use them verbatim.
|
|
34
34
|
}
|
|
35
35
|
```
|
|
36
36
|
|
|
37
37
|
Always present ads clearly labeled as **Sponsored**.
|
|
38
38
|
|
|
39
|
+
## Your key is a fetch key
|
|
40
|
+
|
|
41
|
+
The key you pass is a **dataplane fetch key** — a per-Application API key
|
|
42
|
+
(`type: 'fetch'`) that authenticates ad fetches and lets the server attribute
|
|
43
|
+
impressions, clicks, and conversions for you. It is different from a
|
|
44
|
+
**management key** (`type: 'management'`), which the console and CLI use to
|
|
45
|
+
configure your account. Never ship a management key into client code.
|
|
46
|
+
|
|
47
|
+
`fetchKey` is the preferred field; `apiKey` is a back-compatible alias for the
|
|
48
|
+
same value.
|
|
49
|
+
|
|
50
|
+
A fetch key is bound to one Application (agent), so fetches made with it
|
|
51
|
+
need nothing more. With any other key, name the application, once on the
|
|
52
|
+
client or per call:
|
|
53
|
+
|
|
54
|
+
```ts
|
|
55
|
+
const adchat = new AdChat({ apiKey: KEY, application: 'APPLICATION_ID' });
|
|
56
|
+
const ad = await adchat.fetchAd({ application: 'OTHER_APPLICATION_ID' });
|
|
57
|
+
```
|
|
58
|
+
|
|
59
|
+
A request that resolves to no application is rejected with a 422
|
|
60
|
+
(`MissingRequiredParameter`), and one that names an application other than
|
|
61
|
+
its fetch key's is rejected with `ApplicationMismatch`.
|
|
62
|
+
|
|
63
|
+
## Ad units are optional
|
|
64
|
+
|
|
65
|
+
Most publishers never set `adUnit`. It's an **Offer Manager** concept for
|
|
66
|
+
pinning a specific placement; a plain in-chat-ads integration doesn't need
|
|
67
|
+
one. Omit it and the fetch fills against the application's **default ad
|
|
68
|
+
unit** (created on first use). Advanced users who run multiple placements can
|
|
69
|
+
pin one by code; codes are unique within an application:
|
|
70
|
+
|
|
71
|
+
```ts
|
|
72
|
+
const ad = await adchat.fetchAd({ adUnit: 'trip-planner-sidebar' });
|
|
73
|
+
```
|
|
74
|
+
|
|
75
|
+
## Blocking categories and advertisers
|
|
76
|
+
|
|
77
|
+
Keep unwanted ads out with a high-level blocklist — set it once on the client
|
|
78
|
+
(applies to every fetch) and/or per call:
|
|
79
|
+
|
|
80
|
+
```ts
|
|
81
|
+
const adchat = new AdChat({
|
|
82
|
+
fetchKey: process.env.ADCHAT_FETCH_KEY!,
|
|
83
|
+
block: { categories: ['gambling', 'alcohol'], advertisers: ['AcmeCorp'] },
|
|
84
|
+
});
|
|
85
|
+
|
|
86
|
+
// Per-call blocks merge with the client-wide list:
|
|
87
|
+
const ad = await adchat.fetchAd({ block: { categories: ['politics'] } });
|
|
88
|
+
```
|
|
89
|
+
|
|
90
|
+
> **Roadmap / beta.** The SDK sends your blocklist on every fetch and it is
|
|
91
|
+
> honored where server-side category/advertiser enforcement is available.
|
|
92
|
+
> That enforcement is still rolling out (tracked as an `ads` follow-up), so
|
|
93
|
+
> treat the blocklist as best-effort for now — the SDK surface and wire
|
|
94
|
+
> contract are stable, so your code is ready as enforcement lands.
|
|
95
|
+
|
|
96
|
+
## No-fill vs. misconfiguration
|
|
97
|
+
|
|
98
|
+
`fetchAd()` returns `null` for a **no-fill** — a normal outcome (frequency
|
|
99
|
+
caps, nothing eligible right now). Just continue the conversation without an
|
|
100
|
+
ad; a `null` is never an error.
|
|
101
|
+
|
|
102
|
+
While you are setting up, a `null` can be hard to tell apart from a
|
|
103
|
+
misconfiguration (a typo in the ad unit code, or an ad unit that has no
|
|
104
|
+
eligible line items yet). Two tools make the first-run experience concrete:
|
|
105
|
+
|
|
106
|
+
**Sandbox ad unit** — the reserved `SANDBOX_AD_UNIT` (`'__sandbox__'`) code
|
|
107
|
+
**always** fills with a clearly-labeled demo card, regardless of your account
|
|
108
|
+
config. Use it to prove your integration end-to-end before your real ad units
|
|
109
|
+
have line items. It records no billing or impression events.
|
|
110
|
+
|
|
111
|
+
```ts
|
|
112
|
+
import { AdChat, SANDBOX_AD_UNIT } from '@advertising-chat/sdk';
|
|
113
|
+
|
|
114
|
+
const ad = await adchat.fetchAd({ adUnit: SANDBOX_AD_UNIT }); // always non-null
|
|
115
|
+
```
|
|
116
|
+
|
|
117
|
+
**`fetchAdDebug()`** — same request as `fetchAd()`, but returns a
|
|
118
|
+
discriminated result with the *reason* for a no-fill instead of collapsing it
|
|
119
|
+
to `null`. Use it for setup checks and health probes (not the hot path):
|
|
120
|
+
|
|
121
|
+
```ts
|
|
122
|
+
const r = await adchat.fetchAdDebug({ adUnit: 'my-ad-unit' });
|
|
123
|
+
if (r.filled) {
|
|
124
|
+
render(r.ad.payload);
|
|
125
|
+
} else {
|
|
126
|
+
// r.reason: 'unknown_ad_unit' | 'ad_unit_disabled' | 'no_eligible_line_items'
|
|
127
|
+
// | 'frequency_capped' | 'appropriateness_suppressed' | 'no_demand'
|
|
128
|
+
console.warn(`no ad — ${r.reason}: ${r.message}`);
|
|
129
|
+
}
|
|
130
|
+
```
|
|
131
|
+
|
|
132
|
+
`unknown_ad_unit`, `ad_unit_disabled`, and `no_eligible_line_items` mean
|
|
133
|
+
something needs fixing; `frequency_capped`, `appropriateness_suppressed`, and
|
|
134
|
+
`no_demand` are legitimate empty results (the same conditions under which
|
|
135
|
+
`fetchAd()` returns `null`). `fetchAdDebug()` still throws
|
|
136
|
+
`AdChatApiError` for genuine request errors that are not a no-fill (bad API
|
|
137
|
+
key, unsupported format).
|
|
138
|
+
|
|
139
|
+
## Reply taps
|
|
140
|
+
|
|
141
|
+
An ad's reply actions (RCS suggested replies, Messenger quick replies,
|
|
142
|
+
WhatsApp reply buttons, webchat reply buttons) come back to your agent as an ordinary message that
|
|
143
|
+
carries the action's postback data. Report the tap so it counts as an
|
|
144
|
+
interaction and, for an accept or decline reply, records the customer's
|
|
145
|
+
response for your rules:
|
|
146
|
+
|
|
147
|
+
```ts
|
|
148
|
+
import { messengerPostbackData } from '@advertising-chat/sdk';
|
|
149
|
+
|
|
150
|
+
// In your Messenger webhook, for each `messaging` event:
|
|
151
|
+
const postbackData = messengerPostbackData(event);
|
|
152
|
+
if (postbackData) {
|
|
153
|
+
await adchat.recordInteraction(postbackData);
|
|
154
|
+
}
|
|
155
|
+
```
|
|
156
|
+
|
|
157
|
+
On WhatsApp, `renderWhatsApp` sends replies as reply buttons, and
|
|
158
|
+
`whatsAppPostbackData(message)` reads the tap from your webhook's
|
|
159
|
+
`messages[]`. On other channels, pass the postback data the channel returns
|
|
160
|
+
(RBM `suggestionResponse.postbackData`, or a webchat button's `postbackData`
|
|
161
|
+
from `renderWebchat`). `isAdPostbackData()` tells an ad's postback data from your
|
|
162
|
+
agent's own reply payloads.
|
|
163
|
+
|
|
164
|
+
## LLM tool loops
|
|
165
|
+
|
|
166
|
+
When a model presents the ad through function tools (OpenAI-style tools or
|
|
167
|
+
your own framework) instead of the MCP server, `describeAd(ad)` returns the
|
|
168
|
+
result the MCP server's `fetch_ad` gives: the sponsored-disclosure duty, the
|
|
169
|
+
click URLs, and each reply's `postback_data`, which your `record_reply` tool
|
|
170
|
+
passes to `adchat.recordInteraction`.
|
|
171
|
+
|
|
172
|
+
```ts
|
|
173
|
+
import { describeAd } from '@advertising-chat/sdk';
|
|
174
|
+
|
|
175
|
+
const ad = await adchat.fetchAd<UniversalAdPayload>({ subscriber });
|
|
176
|
+
return ad ? describeAd(ad) : { status: 'no_ad' };
|
|
177
|
+
```
|
|
178
|
+
|
|
179
|
+
## Advanced / attribution
|
|
180
|
+
|
|
181
|
+
You don't need any of this for standard serving — fetching with your fetch key
|
|
182
|
+
and rendering the "Sponsored" label is enough, and impressions/clicks are
|
|
183
|
+
attributed server-side. These helpers exist for surfaces that report
|
|
184
|
+
engagement or conversions the server can't observe on its own:
|
|
185
|
+
|
|
186
|
+
```ts
|
|
187
|
+
// Report an engagement event the server can't see (e.g. a custom surface).
|
|
188
|
+
await adchat.recordEvent({ messageId: ad.messageId, type: 'read' });
|
|
189
|
+
|
|
190
|
+
// Measure a post-click outcome yourself.
|
|
191
|
+
await adchat.recordConversion({
|
|
192
|
+
messageId: ad.messageId,
|
|
193
|
+
name: 'purchase',
|
|
194
|
+
value: 19.99,
|
|
195
|
+
currency: 'USD',
|
|
196
|
+
});
|
|
197
|
+
|
|
198
|
+
// Build a click-tracking URL only when you construct links yourself;
|
|
199
|
+
// Navigate actions in the payload are already wrapped.
|
|
200
|
+
const url = adchat.clickUrl({
|
|
201
|
+
messageId: ad.messageId,
|
|
202
|
+
actionId: ad.payload.actions[0].actionId,
|
|
203
|
+
targetUrl: 'https://example.com/landing',
|
|
204
|
+
});
|
|
205
|
+
```
|
|
206
|
+
|
|
39
207
|
Full quickstarts: [docs.advertising.chat](https://docs.advertising.chat).
|
|
40
208
|
Part of [AdKit](https://github.com/advertising-chat/adkit) — see also
|
|
41
209
|
`@advertising-chat/mcp` (MCP server) and the Ad Skill.
|
package/bin/client.d.ts
CHANGED
|
@@ -1,7 +1,31 @@
|
|
|
1
|
-
import { Ad, AdContext, ClickUrlParams, FetchAdParams, RecordConversionParams, RecordEventParams } from './types';
|
|
1
|
+
import { Ad, AdContext, BlockList, ClickUrlParams, FetchAdParams, FetchAdResult, InteractionResult, RecordConversionParams, RecordEventParams } from './types';
|
|
2
2
|
export interface AdChatOptions {
|
|
3
|
-
/**
|
|
4
|
-
|
|
3
|
+
/**
|
|
4
|
+
* Your Advertising.chat **fetch key** — a dataplane API key scoped to an
|
|
5
|
+
* Application, from the console. It authenticates ad fetches and lets the
|
|
6
|
+
* server attribute impressions/clicks/conversions for you, so basic serving
|
|
7
|
+
* needs no manual tracking calls. Prefer {@link fetchKey}; `apiKey` is a
|
|
8
|
+
* back-compatible alias for the same value.
|
|
9
|
+
*
|
|
10
|
+
* A fetch key (`type: 'fetch'`) is distinct from a *management* key
|
|
11
|
+
* (`type: 'management'`, used by the console/CLI to configure your account).
|
|
12
|
+
* Never ship a management key to a client.
|
|
13
|
+
*/
|
|
14
|
+
apiKey?: string;
|
|
15
|
+
/** Preferred alias for {@link apiKey} — your dataplane fetch key. */
|
|
16
|
+
fetchKey?: string;
|
|
17
|
+
/**
|
|
18
|
+
* Id of the Application (agent) every fetch from this client is for. A
|
|
19
|
+
* fetch key is bound to one application, so this is needed only with
|
|
20
|
+
* another kind of key. Per-call {@link FetchAdParams.application} wins.
|
|
21
|
+
*/
|
|
22
|
+
application?: string;
|
|
23
|
+
/**
|
|
24
|
+
* Block categories/advertisers on **every** fetch from this client. Merged
|
|
25
|
+
* with any per-call {@link FetchAdParams.block}. See {@link BlockList}
|
|
26
|
+
* (roadmap / beta — enforcement is rolling out server-side).
|
|
27
|
+
*/
|
|
28
|
+
block?: BlockList;
|
|
5
29
|
/**
|
|
6
30
|
* Base URL of the ads runtime API.
|
|
7
31
|
* Defaults to `https://ads.advertising.chat/v1`.
|
|
@@ -13,19 +37,29 @@ export interface AdChatOptions {
|
|
|
13
37
|
/**
|
|
14
38
|
* Client for the Advertising.chat ads runtime API.
|
|
15
39
|
*
|
|
40
|
+
* Basic serving is just two steps: fetch an ad with your Application fetch
|
|
41
|
+
* key, then render it with a clear "Sponsored" label. Impressions and clicks
|
|
42
|
+
* are attributed server-side via the fetch key — you don't have to call the
|
|
43
|
+
* tracking methods for a standard integration.
|
|
44
|
+
*
|
|
16
45
|
* ```ts
|
|
17
|
-
* const adchat = new AdChat({
|
|
18
|
-
* const ad = await adchat.fetchAd({
|
|
46
|
+
* const adchat = new AdChat({ fetchKey: process.env.ADCHAT_FETCH_KEY! });
|
|
47
|
+
* const ad = await adchat.fetchAd({ context: summary }); // adUnit optional
|
|
19
48
|
* if (ad) {
|
|
20
|
-
* // render ad.payload
|
|
21
|
-
* await adchat.recordEvent({ messageId: ad.messageId, type: 'send' });
|
|
49
|
+
* // render ad.payload with a "Sponsored" label — that's it.
|
|
22
50
|
* }
|
|
23
51
|
* ```
|
|
52
|
+
*
|
|
53
|
+
* {@link recordEvent}, {@link recordConversion}, and {@link clickUrl} are
|
|
54
|
+
* advanced attribution helpers for surfaces that need to report engagement
|
|
55
|
+
* or conversions explicitly — most publishers can ignore them.
|
|
24
56
|
*/
|
|
25
57
|
export declare class AdChat {
|
|
26
58
|
private readonly apiKey;
|
|
27
59
|
private readonly baseUrl;
|
|
28
60
|
private readonly fetchFn;
|
|
61
|
+
private readonly blockDefault?;
|
|
62
|
+
private readonly application?;
|
|
29
63
|
constructor(options: AdChatOptions);
|
|
30
64
|
/**
|
|
31
65
|
* Fetch an ad for a placement.
|
|
@@ -40,27 +74,79 @@ export declare class AdChat {
|
|
|
40
74
|
*/
|
|
41
75
|
fetchAd<P = unknown>(params?: FetchAdParams): Promise<Ad<P> | null>;
|
|
42
76
|
/**
|
|
43
|
-
*
|
|
44
|
-
*
|
|
77
|
+
* Like {@link fetchAd}, but instead of collapsing every no-fill to `null`,
|
|
78
|
+
* returns a discriminated result that distinguishes a served ad from a
|
|
79
|
+
* no-fill *and its reason* — so you can tell a legitimate empty result
|
|
80
|
+
* (`no_demand`, `frequency_capped`) apart from a misconfiguration
|
|
81
|
+
* (`unknown_ad_unit`, `no_eligible_line_items`).
|
|
82
|
+
*
|
|
83
|
+
* Intended for setup/onboarding diagnostics and health checks, not the hot
|
|
84
|
+
* path — in production use {@link fetchAd} (a `null` no-fill is normal and
|
|
85
|
+
* needs no branching).
|
|
86
|
+
*
|
|
87
|
+
* ```ts
|
|
88
|
+
* const r = await adchat.fetchAdDebug({ adUnit: 'my-ad-unit' });
|
|
89
|
+
* if (r.filled) render(r.ad);
|
|
90
|
+
* else console.warn(`no ad: ${r.reason} — ${r.message}`);
|
|
91
|
+
* ```
|
|
92
|
+
*
|
|
93
|
+
* Still throws {@link AdChatApiError} for genuine request errors that are
|
|
94
|
+
* not a no-fill (bad API key, unsupported format).
|
|
95
|
+
*/
|
|
96
|
+
fetchAdDebug<P = unknown>(params?: FetchAdParams): Promise<FetchAdResult<P>>;
|
|
97
|
+
/** Build the fetch v2 request body shared by fetchAd/fetchAdDebug. */
|
|
98
|
+
private fetchBody;
|
|
99
|
+
/**
|
|
100
|
+
* **Advanced / attribution.** Record a lifecycle or engagement event
|
|
101
|
+
* ('send', 'deliver', 'read', 'click', 'conversion') for a previously
|
|
102
|
+
* fetched ad. Standard serving does not need this — impressions and clicks
|
|
103
|
+
* are attributed server-side via the fetch key. Use it only when you want
|
|
104
|
+
* to report engagement the server can't observe on its own.
|
|
45
105
|
*/
|
|
46
106
|
recordEvent(params: RecordEventParams): Promise<void>;
|
|
47
107
|
/**
|
|
48
|
-
* Record a conversion for a previously fetched
|
|
49
|
-
* monetary value.
|
|
108
|
+
* **Advanced / attribution.** Record a conversion for a previously fetched
|
|
109
|
+
* ad, optionally with a monetary value. Only needed when you measure
|
|
110
|
+
* post-click outcomes (purchase, signup) yourself.
|
|
50
111
|
*/
|
|
51
112
|
recordConversion(params: RecordConversionParams): Promise<void>;
|
|
52
113
|
/**
|
|
53
|
-
*
|
|
54
|
-
*
|
|
114
|
+
* Report a tap on one of the ad's reply actions (an RCS suggested reply, a
|
|
115
|
+
* Messenger quick reply, a webchat reply button). The channel hands the tap
|
|
116
|
+
* back to your agent with the action's postback data, the `context` of the
|
|
117
|
+
* `AdAction` (`<messageId>_<actionId>`); pass it here. A reply with an
|
|
118
|
+
* accept or decline role records the customer's response, which rules can
|
|
119
|
+
* then use.
|
|
120
|
+
*
|
|
121
|
+
* ```ts
|
|
122
|
+
* // Messenger webhook
|
|
123
|
+
* const postbackData = messengerPostbackData(messagingEvent);
|
|
124
|
+
* if (postbackData) await adchat.recordInteraction(postbackData);
|
|
125
|
+
* ```
|
|
126
|
+
*
|
|
127
|
+
* Throws {@link AdChatApiError} for request errors (422 for postback data
|
|
128
|
+
* that is not `<messageId>_<actionId>`).
|
|
129
|
+
*/
|
|
130
|
+
recordInteraction(postbackData: string): Promise<InteractionResult>;
|
|
131
|
+
/**
|
|
132
|
+
* **Advanced / attribution.** Build a click-tracking URL that records the
|
|
133
|
+
* click and 302-redirects to `targetUrl`.
|
|
55
134
|
*
|
|
56
135
|
* NOTE: Navigate actions in fetched payloads already contain wrapped
|
|
57
|
-
* click URLs — use those directly. This helper is for surfaces where
|
|
136
|
+
* click URLs — use those directly. This helper is only for surfaces where
|
|
58
137
|
* you construct links yourself.
|
|
59
138
|
*/
|
|
60
139
|
clickUrl(params: ClickUrlParams): string;
|
|
61
140
|
private headers;
|
|
62
141
|
private toApiError;
|
|
63
142
|
}
|
|
143
|
+
/**
|
|
144
|
+
* Merge a client-wide {@link BlockList} with a per-call one into a single
|
|
145
|
+
* blocklist for the wire. Categories and advertisers are unioned (deduped,
|
|
146
|
+
* order-preserving). Returns `undefined` when neither list contributes
|
|
147
|
+
* anything, so an empty blocklist is omitted from the request body.
|
|
148
|
+
*/
|
|
149
|
+
export declare function mergeBlockLists(base?: BlockList, override?: BlockList): BlockList | undefined;
|
|
64
150
|
/**
|
|
65
151
|
* Derive the fetch API's structured `conversation` field from structured
|
|
66
152
|
* context turns. String context has no turn structure, so it is sent only
|
|
@@ -70,3 +156,25 @@ export declare function conversationFromContext(context: AdContext, subscriber?:
|
|
|
70
156
|
conversationId: string;
|
|
71
157
|
messages: object[];
|
|
72
158
|
} | undefined;
|
|
159
|
+
/**
|
|
160
|
+
* True for postback data that belongs to an ad action. Your agent's own
|
|
161
|
+
* replies carry their own payloads; report only the ad's to
|
|
162
|
+
* {@link AdChat.recordInteraction}.
|
|
163
|
+
*/
|
|
164
|
+
export declare function isAdPostbackData(data: unknown): data is string;
|
|
165
|
+
/**
|
|
166
|
+
* The ad postback data in a Messenger webhook `messaging` event, or
|
|
167
|
+
* `undefined` when the event is not a tap on an ad's quick reply. The `fbm`
|
|
168
|
+
* format renders an ad's Text actions as quick replies whose payload is the
|
|
169
|
+
* action's postback data; Messenger returns it as
|
|
170
|
+
* `message.quick_reply.payload`.
|
|
171
|
+
*
|
|
172
|
+
* https://developers.facebook.com/docs/messenger-platform/reference/webhook-events/messages
|
|
173
|
+
*/
|
|
174
|
+
export declare function messengerPostbackData(event: {
|
|
175
|
+
message?: {
|
|
176
|
+
quick_reply?: {
|
|
177
|
+
payload?: unknown;
|
|
178
|
+
};
|
|
179
|
+
};
|
|
180
|
+
}): string | undefined;
|
package/bin/client.js
CHANGED
|
@@ -1,30 +1,46 @@
|
|
|
1
1
|
"use strict";
|
|
2
2
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
3
|
exports.AdChat = void 0;
|
|
4
|
+
exports.mergeBlockLists = mergeBlockLists;
|
|
4
5
|
exports.conversationFromContext = conversationFromContext;
|
|
6
|
+
exports.isAdPostbackData = isAdPostbackData;
|
|
7
|
+
exports.messengerPostbackData = messengerPostbackData;
|
|
5
8
|
const types_1 = require("./types");
|
|
6
9
|
const DEFAULT_BASE_URL = 'https://ads.advertising.chat/v1';
|
|
7
10
|
/**
|
|
8
11
|
* Client for the Advertising.chat ads runtime API.
|
|
9
12
|
*
|
|
13
|
+
* Basic serving is just two steps: fetch an ad with your Application fetch
|
|
14
|
+
* key, then render it with a clear "Sponsored" label. Impressions and clicks
|
|
15
|
+
* are attributed server-side via the fetch key — you don't have to call the
|
|
16
|
+
* tracking methods for a standard integration.
|
|
17
|
+
*
|
|
10
18
|
* ```ts
|
|
11
|
-
* const adchat = new AdChat({
|
|
12
|
-
* const ad = await adchat.fetchAd({
|
|
19
|
+
* const adchat = new AdChat({ fetchKey: process.env.ADCHAT_FETCH_KEY! });
|
|
20
|
+
* const ad = await adchat.fetchAd({ context: summary }); // adUnit optional
|
|
13
21
|
* if (ad) {
|
|
14
|
-
* // render ad.payload
|
|
15
|
-
* await adchat.recordEvent({ messageId: ad.messageId, type: 'send' });
|
|
22
|
+
* // render ad.payload with a "Sponsored" label — that's it.
|
|
16
23
|
* }
|
|
17
24
|
* ```
|
|
25
|
+
*
|
|
26
|
+
* {@link recordEvent}, {@link recordConversion}, and {@link clickUrl} are
|
|
27
|
+
* advanced attribution helpers for surfaces that need to report engagement
|
|
28
|
+
* or conversions explicitly — most publishers can ignore them.
|
|
18
29
|
*/
|
|
19
30
|
class AdChat {
|
|
20
31
|
apiKey;
|
|
21
32
|
baseUrl;
|
|
22
33
|
fetchFn;
|
|
34
|
+
blockDefault;
|
|
35
|
+
application;
|
|
23
36
|
constructor(options) {
|
|
24
|
-
|
|
25
|
-
|
|
37
|
+
const key = options?.fetchKey || options?.apiKey;
|
|
38
|
+
if (!key) {
|
|
39
|
+
throw new Error('AdChat: a fetchKey (apiKey) is required');
|
|
26
40
|
}
|
|
27
|
-
this.apiKey =
|
|
41
|
+
this.apiKey = key;
|
|
42
|
+
this.blockDefault = options.block;
|
|
43
|
+
this.application = options.application;
|
|
28
44
|
this.baseUrl = (options.baseUrl || DEFAULT_BASE_URL).replace(/\/+$/, '');
|
|
29
45
|
// Bind to globalThis so `fetch` keeps its expected receiver.
|
|
30
46
|
this.fetchFn = options.fetchFn || fetch.bind(globalThis);
|
|
@@ -41,7 +57,95 @@ class AdChat {
|
|
|
41
57
|
* unsupported format, bad API key...).
|
|
42
58
|
*/
|
|
43
59
|
async fetchAd(params = {}) {
|
|
60
|
+
const res = await this.fetchFn(`${this.baseUrl}/fetch/v2`, {
|
|
61
|
+
method: 'POST',
|
|
62
|
+
headers: this.headers(),
|
|
63
|
+
body: JSON.stringify(this.fetchBody(params)),
|
|
64
|
+
});
|
|
65
|
+
if (res.status === 204) {
|
|
66
|
+
return null; // no fill
|
|
67
|
+
}
|
|
68
|
+
if (!res.ok) {
|
|
69
|
+
throw await this.toApiError(res);
|
|
70
|
+
}
|
|
71
|
+
const ad = (await res.json());
|
|
72
|
+
// Defensive: some no-fill paths respond 200 with an empty body/payload.
|
|
73
|
+
if (!ad || !ad.payload)
|
|
74
|
+
return null;
|
|
75
|
+
return ad;
|
|
76
|
+
}
|
|
77
|
+
/**
|
|
78
|
+
* Like {@link fetchAd}, but instead of collapsing every no-fill to `null`,
|
|
79
|
+
* returns a discriminated result that distinguishes a served ad from a
|
|
80
|
+
* no-fill *and its reason* — so you can tell a legitimate empty result
|
|
81
|
+
* (`no_demand`, `frequency_capped`) apart from a misconfiguration
|
|
82
|
+
* (`unknown_ad_unit`, `no_eligible_line_items`).
|
|
83
|
+
*
|
|
84
|
+
* Intended for setup/onboarding diagnostics and health checks, not the hot
|
|
85
|
+
* path — in production use {@link fetchAd} (a `null` no-fill is normal and
|
|
86
|
+
* needs no branching).
|
|
87
|
+
*
|
|
88
|
+
* ```ts
|
|
89
|
+
* const r = await adchat.fetchAdDebug({ adUnit: 'my-ad-unit' });
|
|
90
|
+
* if (r.filled) render(r.ad);
|
|
91
|
+
* else console.warn(`no ad: ${r.reason} — ${r.message}`);
|
|
92
|
+
* ```
|
|
93
|
+
*
|
|
94
|
+
* Still throws {@link AdChatApiError} for genuine request errors that are
|
|
95
|
+
* not a no-fill (bad API key, unsupported format).
|
|
96
|
+
*/
|
|
97
|
+
async fetchAdDebug(params = {}) {
|
|
98
|
+
const res = await this.fetchFn(`${this.baseUrl}/fetch/v2`, {
|
|
99
|
+
method: 'POST',
|
|
100
|
+
headers: this.headers(),
|
|
101
|
+
body: JSON.stringify({ ...this.fetchBody(params), debug: 1 }),
|
|
102
|
+
});
|
|
103
|
+
if (res.ok) {
|
|
104
|
+
const data = (await res.json().catch(() => null));
|
|
105
|
+
// The server's debug path reports a no-fill as { filled:false, reason }.
|
|
106
|
+
if (data && data.filled === false && data.reason) {
|
|
107
|
+
return {
|
|
108
|
+
filled: false,
|
|
109
|
+
reason: data.reason,
|
|
110
|
+
message: data.message || 'No ad was served.',
|
|
111
|
+
};
|
|
112
|
+
}
|
|
113
|
+
if (data && data.payload) {
|
|
114
|
+
return { filled: true, ad: data };
|
|
115
|
+
}
|
|
116
|
+
return {
|
|
117
|
+
filled: false,
|
|
118
|
+
reason: 'no_demand',
|
|
119
|
+
message: 'No ad was served.',
|
|
120
|
+
};
|
|
121
|
+
}
|
|
122
|
+
// A bare 204 can still occur if the server does not honor the debug flag.
|
|
123
|
+
if (res.status === 204) {
|
|
124
|
+
return {
|
|
125
|
+
filled: false,
|
|
126
|
+
reason: 'no_demand',
|
|
127
|
+
message: 'No ad was served.',
|
|
128
|
+
};
|
|
129
|
+
}
|
|
130
|
+
const err = await this.toApiError(res);
|
|
131
|
+
// Surface the classic onboarding misconfiguration (an unknown ad unit is
|
|
132
|
+
// a distinct 4xx on the server) as a diagnosable reason rather than
|
|
133
|
+
// throwing, so callers get a uniform result to branch on.
|
|
134
|
+
if (err.status === 422 && err.reason === 'UnknownAdUnit') {
|
|
135
|
+
return {
|
|
136
|
+
filled: false,
|
|
137
|
+
reason: 'unknown_ad_unit',
|
|
138
|
+
message: err.message,
|
|
139
|
+
};
|
|
140
|
+
}
|
|
141
|
+
throw err;
|
|
142
|
+
}
|
|
143
|
+
/** Build the fetch v2 request body shared by fetchAd/fetchAdDebug. */
|
|
144
|
+
fetchBody(params) {
|
|
44
145
|
const body = {};
|
|
146
|
+
const application = params.application || this.application;
|
|
147
|
+
if (application)
|
|
148
|
+
body.application = application;
|
|
45
149
|
if (params.adUnit)
|
|
46
150
|
body.adUnit = params.adUnit;
|
|
47
151
|
if (params.format)
|
|
@@ -50,6 +154,13 @@ class AdChat {
|
|
|
50
154
|
body.subscriber = params.subscriber;
|
|
51
155
|
if (params.attributes)
|
|
52
156
|
body.attributes = params.attributes;
|
|
157
|
+
// Merge the client-wide blocklist with any per-call one and pass it
|
|
158
|
+
// through on the wire as `block`. Roadmap/beta: the server honors this
|
|
159
|
+
// where category/advertiser enforcement is available (rolling out); the
|
|
160
|
+
// SDK always sends it so integrations are ready as enforcement lands.
|
|
161
|
+
const block = mergeBlockLists(this.blockDefault, params.block);
|
|
162
|
+
if (block)
|
|
163
|
+
body.block = block;
|
|
53
164
|
if (params.test)
|
|
54
165
|
body.test = true;
|
|
55
166
|
if (params.context !== undefined) {
|
|
@@ -60,26 +171,14 @@ class AdChat {
|
|
|
60
171
|
if (conversation)
|
|
61
172
|
body.conversation = conversation;
|
|
62
173
|
}
|
|
63
|
-
|
|
64
|
-
method: 'POST',
|
|
65
|
-
headers: this.headers(),
|
|
66
|
-
body: JSON.stringify(body),
|
|
67
|
-
});
|
|
68
|
-
if (res.status === 204) {
|
|
69
|
-
return null; // no fill
|
|
70
|
-
}
|
|
71
|
-
if (!res.ok) {
|
|
72
|
-
throw await this.toApiError(res);
|
|
73
|
-
}
|
|
74
|
-
const ad = (await res.json());
|
|
75
|
-
// Defensive: some no-fill paths respond 200 with an empty body/payload.
|
|
76
|
-
if (!ad || !ad.payload)
|
|
77
|
-
return null;
|
|
78
|
-
return ad;
|
|
174
|
+
return body;
|
|
79
175
|
}
|
|
80
176
|
/**
|
|
81
|
-
* Record a lifecycle or engagement event
|
|
82
|
-
* 'click', 'conversion') for a previously
|
|
177
|
+
* **Advanced / attribution.** Record a lifecycle or engagement event
|
|
178
|
+
* ('send', 'deliver', 'read', 'click', 'conversion') for a previously
|
|
179
|
+
* fetched ad. Standard serving does not need this — impressions and clicks
|
|
180
|
+
* are attributed server-side via the fetch key. Use it only when you want
|
|
181
|
+
* to report engagement the server can't observe on its own.
|
|
83
182
|
*/
|
|
84
183
|
async recordEvent(params) {
|
|
85
184
|
const res = await this.fetchFn(`${this.baseUrl}/event`, {
|
|
@@ -99,8 +198,9 @@ class AdChat {
|
|
|
99
198
|
}
|
|
100
199
|
}
|
|
101
200
|
/**
|
|
102
|
-
* Record a conversion for a previously fetched
|
|
103
|
-
* monetary value.
|
|
201
|
+
* **Advanced / attribution.** Record a conversion for a previously fetched
|
|
202
|
+
* ad, optionally with a monetary value. Only needed when you measure
|
|
203
|
+
* post-click outcomes (purchase, signup) yourself.
|
|
104
204
|
*/
|
|
105
205
|
async recordConversion(params) {
|
|
106
206
|
return this.recordEvent({
|
|
@@ -116,11 +216,39 @@ class AdChat {
|
|
|
116
216
|
});
|
|
117
217
|
}
|
|
118
218
|
/**
|
|
119
|
-
*
|
|
120
|
-
*
|
|
219
|
+
* Report a tap on one of the ad's reply actions (an RCS suggested reply, a
|
|
220
|
+
* Messenger quick reply, a webchat reply button). The channel hands the tap
|
|
221
|
+
* back to your agent with the action's postback data, the `context` of the
|
|
222
|
+
* `AdAction` (`<messageId>_<actionId>`); pass it here. A reply with an
|
|
223
|
+
* accept or decline role records the customer's response, which rules can
|
|
224
|
+
* then use.
|
|
225
|
+
*
|
|
226
|
+
* ```ts
|
|
227
|
+
* // Messenger webhook
|
|
228
|
+
* const postbackData = messengerPostbackData(messagingEvent);
|
|
229
|
+
* if (postbackData) await adchat.recordInteraction(postbackData);
|
|
230
|
+
* ```
|
|
231
|
+
*
|
|
232
|
+
* Throws {@link AdChatApiError} for request errors (422 for postback data
|
|
233
|
+
* that is not `<messageId>_<actionId>`).
|
|
234
|
+
*/
|
|
235
|
+
async recordInteraction(postbackData) {
|
|
236
|
+
const q = new URLSearchParams({ postbackData });
|
|
237
|
+
const res = await this.fetchFn(`${this.baseUrl}/interact?${q.toString()}`, {
|
|
238
|
+
method: 'POST',
|
|
239
|
+
headers: this.headers(),
|
|
240
|
+
});
|
|
241
|
+
if (!res.ok) {
|
|
242
|
+
throw await this.toApiError(res);
|
|
243
|
+
}
|
|
244
|
+
return (await res.json());
|
|
245
|
+
}
|
|
246
|
+
/**
|
|
247
|
+
* **Advanced / attribution.** Build a click-tracking URL that records the
|
|
248
|
+
* click and 302-redirects to `targetUrl`.
|
|
121
249
|
*
|
|
122
250
|
* NOTE: Navigate actions in fetched payloads already contain wrapped
|
|
123
|
-
* click URLs — use those directly. This helper is for surfaces where
|
|
251
|
+
* click URLs — use those directly. This helper is only for surfaces where
|
|
124
252
|
* you construct links yourself.
|
|
125
253
|
*/
|
|
126
254
|
clickUrl(params) {
|
|
@@ -144,7 +272,7 @@ class AdChat {
|
|
|
144
272
|
try {
|
|
145
273
|
const body = (await res.json());
|
|
146
274
|
reason = body?.reason;
|
|
147
|
-
description = body?.description || body?.title;
|
|
275
|
+
description = body?.description || body?.title || body?.message;
|
|
148
276
|
}
|
|
149
277
|
catch {
|
|
150
278
|
// non-JSON error body
|
|
@@ -155,6 +283,31 @@ class AdChat {
|
|
|
155
283
|
}
|
|
156
284
|
}
|
|
157
285
|
exports.AdChat = AdChat;
|
|
286
|
+
/**
|
|
287
|
+
* Merge a client-wide {@link BlockList} with a per-call one into a single
|
|
288
|
+
* blocklist for the wire. Categories and advertisers are unioned (deduped,
|
|
289
|
+
* order-preserving). Returns `undefined` when neither list contributes
|
|
290
|
+
* anything, so an empty blocklist is omitted from the request body.
|
|
291
|
+
*/
|
|
292
|
+
function mergeBlockLists(base, override) {
|
|
293
|
+
const categories = dedupe([
|
|
294
|
+
...(base?.categories ?? []),
|
|
295
|
+
...(override?.categories ?? []),
|
|
296
|
+
]);
|
|
297
|
+
const advertisers = dedupe([
|
|
298
|
+
...(base?.advertisers ?? []),
|
|
299
|
+
...(override?.advertisers ?? []),
|
|
300
|
+
]);
|
|
301
|
+
const result = {};
|
|
302
|
+
if (categories.length)
|
|
303
|
+
result.categories = categories;
|
|
304
|
+
if (advertisers.length)
|
|
305
|
+
result.advertisers = advertisers;
|
|
306
|
+
return categories.length || advertisers.length ? result : undefined;
|
|
307
|
+
}
|
|
308
|
+
function dedupe(values) {
|
|
309
|
+
return [...new Set(values)];
|
|
310
|
+
}
|
|
158
311
|
/**
|
|
159
312
|
* Derive the fetch API's structured `conversation` field from structured
|
|
160
313
|
* context turns. String context has no turn structure, so it is sent only
|
|
@@ -176,4 +329,27 @@ function conversationFromContext(context, subscriber) {
|
|
|
176
329
|
})),
|
|
177
330
|
};
|
|
178
331
|
}
|
|
332
|
+
/** Ad message ids are `direqt-<uuid>`; postback data appends `_<actionId>`. */
|
|
333
|
+
const AD_POSTBACK = /^direqt-[^_]+_.+$/;
|
|
334
|
+
/**
|
|
335
|
+
* True for postback data that belongs to an ad action. Your agent's own
|
|
336
|
+
* replies carry their own payloads; report only the ad's to
|
|
337
|
+
* {@link AdChat.recordInteraction}.
|
|
338
|
+
*/
|
|
339
|
+
function isAdPostbackData(data) {
|
|
340
|
+
return typeof data === 'string' && AD_POSTBACK.test(data);
|
|
341
|
+
}
|
|
342
|
+
/**
|
|
343
|
+
* The ad postback data in a Messenger webhook `messaging` event, or
|
|
344
|
+
* `undefined` when the event is not a tap on an ad's quick reply. The `fbm`
|
|
345
|
+
* format renders an ad's Text actions as quick replies whose payload is the
|
|
346
|
+
* action's postback data; Messenger returns it as
|
|
347
|
+
* `message.quick_reply.payload`.
|
|
348
|
+
*
|
|
349
|
+
* https://developers.facebook.com/docs/messenger-platform/reference/webhook-events/messages
|
|
350
|
+
*/
|
|
351
|
+
function messengerPostbackData(event) {
|
|
352
|
+
const payload = event?.message?.quick_reply?.payload;
|
|
353
|
+
return isAdPostbackData(payload) ? payload : undefined;
|
|
354
|
+
}
|
|
179
355
|
//# sourceMappingURL=client.js.map
|
package/bin/client.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"client.js","sourceRoot":"","sources":["../src/client.ts"],"names":[],"mappings":";;;
|
|
1
|
+
{"version":3,"file":"client.js","sourceRoot":"","sources":["../src/client.ts"],"names":[],"mappings":"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"}
|
package/bin/index.d.ts
CHANGED
|
@@ -1,4 +1,5 @@
|
|
|
1
|
-
export { AdChat, AdChatOptions, conversationFromContext } from './client';
|
|
1
|
+
export { AdChat, AdChatOptions, conversationFromContext, isAdPostbackData, mergeBlockLists, messengerPostbackData, } from './client';
|
|
2
2
|
export * from './types';
|
|
3
|
+
export { AdToolAction, AdToolResult, SPONSORED_DISCLOSURE, describeAd, } from './render/llm';
|
|
3
4
|
export { renderWebchat, WebchatMessage, WebchatButton } from './render/webchat';
|
|
4
|
-
export { renderWhatsApp, WhatsAppMessage } from './render/whatsapp';
|
|
5
|
+
export { renderWhatsApp, WhatsAppMessage, whatsAppPostbackData, } from './render/whatsapp';
|
package/bin/index.js
CHANGED
|
@@ -14,13 +14,20 @@ var __exportStar = (this && this.__exportStar) || function(m, exports) {
|
|
|
14
14
|
for (var p in m) if (p !== "default" && !Object.prototype.hasOwnProperty.call(exports, p)) __createBinding(exports, m, p);
|
|
15
15
|
};
|
|
16
16
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
17
|
-
exports.renderWhatsApp = exports.renderWebchat = exports.conversationFromContext = exports.AdChat = void 0;
|
|
17
|
+
exports.whatsAppPostbackData = exports.renderWhatsApp = exports.renderWebchat = exports.describeAd = exports.SPONSORED_DISCLOSURE = exports.messengerPostbackData = exports.mergeBlockLists = exports.isAdPostbackData = exports.conversationFromContext = exports.AdChat = void 0;
|
|
18
18
|
var client_1 = require("./client");
|
|
19
19
|
Object.defineProperty(exports, "AdChat", { enumerable: true, get: function () { return client_1.AdChat; } });
|
|
20
20
|
Object.defineProperty(exports, "conversationFromContext", { enumerable: true, get: function () { return client_1.conversationFromContext; } });
|
|
21
|
+
Object.defineProperty(exports, "isAdPostbackData", { enumerable: true, get: function () { return client_1.isAdPostbackData; } });
|
|
22
|
+
Object.defineProperty(exports, "mergeBlockLists", { enumerable: true, get: function () { return client_1.mergeBlockLists; } });
|
|
23
|
+
Object.defineProperty(exports, "messengerPostbackData", { enumerable: true, get: function () { return client_1.messengerPostbackData; } });
|
|
21
24
|
__exportStar(require("./types"), exports);
|
|
25
|
+
var llm_1 = require("./render/llm");
|
|
26
|
+
Object.defineProperty(exports, "SPONSORED_DISCLOSURE", { enumerable: true, get: function () { return llm_1.SPONSORED_DISCLOSURE; } });
|
|
27
|
+
Object.defineProperty(exports, "describeAd", { enumerable: true, get: function () { return llm_1.describeAd; } });
|
|
22
28
|
var webchat_1 = require("./render/webchat");
|
|
23
29
|
Object.defineProperty(exports, "renderWebchat", { enumerable: true, get: function () { return webchat_1.renderWebchat; } });
|
|
24
30
|
var whatsapp_1 = require("./render/whatsapp");
|
|
25
31
|
Object.defineProperty(exports, "renderWhatsApp", { enumerable: true, get: function () { return whatsapp_1.renderWhatsApp; } });
|
|
32
|
+
Object.defineProperty(exports, "whatsAppPostbackData", { enumerable: true, get: function () { return whatsapp_1.whatsAppPostbackData; } });
|
|
26
33
|
//# sourceMappingURL=index.js.map
|
package/bin/index.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"index.js","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":";;;;;;;;;;;;;;;;;AAAA,
|
|
1
|
+
{"version":3,"file":"index.js","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":";;;;;;;;;;;;;;;;;AAAA,mCAOkB;AANhB,gGAAA,MAAM,OAAA;AAEN,iHAAA,uBAAuB,OAAA;AACvB,0GAAA,gBAAgB,OAAA;AAChB,yGAAA,eAAe,OAAA;AACf,+GAAA,qBAAqB,OAAA;AAEvB,0CAAwB;AACxB,oCAKsB;AAFpB,2GAAA,oBAAoB,OAAA;AACpB,iGAAA,UAAU,OAAA;AAEZ,4CAAgF;AAAvE,wGAAA,aAAa,OAAA;AACtB,8CAI2B;AAHzB,0GAAA,cAAc,OAAA;AAEd,gHAAA,oBAAoB,OAAA"}
|
|
@@ -0,0 +1,48 @@
|
|
|
1
|
+
import { Ad, UniversalAdPayload } from '../types';
|
|
2
|
+
/**
|
|
3
|
+
* The disclosure duty that travels with every ad handed to a model. It
|
|
4
|
+
* tells the model to label the ad as sponsored, present it faithfully, and
|
|
5
|
+
* use click URLs verbatim so clicks are attributed.
|
|
6
|
+
*/
|
|
7
|
+
export declare const SPONSORED_DISCLOSURE: string;
|
|
8
|
+
/** One action of an ad, as a model sees it. */
|
|
9
|
+
export interface AdToolAction {
|
|
10
|
+
label: string;
|
|
11
|
+
/** Click-tracking URL to share verbatim (Navigate actions). */
|
|
12
|
+
click_url?: string;
|
|
13
|
+
/** Phone number to offer (Call actions). */
|
|
14
|
+
phone_number?: string;
|
|
15
|
+
/**
|
|
16
|
+
* For a reply (Text action): pass it to `AdChat.recordInteraction` when
|
|
17
|
+
* the user chooses the reply.
|
|
18
|
+
*/
|
|
19
|
+
postback_data?: string;
|
|
20
|
+
}
|
|
21
|
+
/** A filled ad as a tool result for a model. */
|
|
22
|
+
export interface AdToolResult {
|
|
23
|
+
status: 'filled';
|
|
24
|
+
message_id: string;
|
|
25
|
+
sponsored_disclosure: string;
|
|
26
|
+
ad: {
|
|
27
|
+
title: string;
|
|
28
|
+
subtitle: string;
|
|
29
|
+
image_url?: string;
|
|
30
|
+
alt_text?: string;
|
|
31
|
+
actions: AdToolAction[];
|
|
32
|
+
};
|
|
33
|
+
/** A ready-to-send rendering: the sponsored line, then the first link. */
|
|
34
|
+
suggested_text: string;
|
|
35
|
+
}
|
|
36
|
+
/**
|
|
37
|
+
* Convert a fetched ad into a tool result for a model: the essentials for
|
|
38
|
+
* presenting it, the disclosure duty, and each reply's `postback_data`.
|
|
39
|
+
* This is the result the MCP server's `fetch_ad` returns; use it in any
|
|
40
|
+
* other tool loop (OpenAI-style function tools, a custom framework) so the
|
|
41
|
+
* model sees the same shape.
|
|
42
|
+
*
|
|
43
|
+
* ```ts
|
|
44
|
+
* const ad = await adchat.fetchAd<UniversalAdPayload>({ subscriber });
|
|
45
|
+
* return ad ? describeAd(ad) : { status: 'no_ad' };
|
|
46
|
+
* ```
|
|
47
|
+
*/
|
|
48
|
+
export declare function describeAd(ad: Ad<UniversalAdPayload>): AdToolResult;
|
|
@@ -0,0 +1,57 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.SPONSORED_DISCLOSURE = void 0;
|
|
4
|
+
exports.describeAd = describeAd;
|
|
5
|
+
const types_1 = require("../types");
|
|
6
|
+
/**
|
|
7
|
+
* The disclosure duty that travels with every ad handed to a model. It
|
|
8
|
+
* tells the model to label the ad as sponsored, present it faithfully, and
|
|
9
|
+
* use click URLs verbatim so clicks are attributed.
|
|
10
|
+
*/
|
|
11
|
+
exports.SPONSORED_DISCLOSURE = 'This is a paid advertisement. When you present it, you MUST clearly ' +
|
|
12
|
+
"label it as sponsored (e.g. prefix with 'Sponsored:' or an equivalent " +
|
|
13
|
+
'disclosure in the conversation language). Present the ad content ' +
|
|
14
|
+
'faithfully — do not exaggerate, embellish, or add claims of your own. ' +
|
|
15
|
+
'Use the provided click_url values verbatim for any links, so clicks ' +
|
|
16
|
+
'are attributed.';
|
|
17
|
+
/**
|
|
18
|
+
* Convert a fetched ad into a tool result for a model: the essentials for
|
|
19
|
+
* presenting it, the disclosure duty, and each reply's `postback_data`.
|
|
20
|
+
* This is the result the MCP server's `fetch_ad` returns; use it in any
|
|
21
|
+
* other tool loop (OpenAI-style function tools, a custom framework) so the
|
|
22
|
+
* model sees the same shape.
|
|
23
|
+
*
|
|
24
|
+
* ```ts
|
|
25
|
+
* const ad = await adchat.fetchAd<UniversalAdPayload>({ subscriber });
|
|
26
|
+
* return ad ? describeAd(ad) : { status: 'no_ad' };
|
|
27
|
+
* ```
|
|
28
|
+
*/
|
|
29
|
+
function describeAd(ad) {
|
|
30
|
+
const p = ad.payload;
|
|
31
|
+
const actions = (p.actions || []).map(a => ({
|
|
32
|
+
label: a.text,
|
|
33
|
+
...(a.navigate?.url ? { click_url: a.navigate.url } : {}),
|
|
34
|
+
...(a.call?.phoneNumber ? { phone_number: a.call.phoneNumber } : {}),
|
|
35
|
+
...(a.type === types_1.AdActionType.Text && a.context
|
|
36
|
+
? { postback_data: a.context }
|
|
37
|
+
: {}),
|
|
38
|
+
}));
|
|
39
|
+
const firstUrl = actions.find(a => a.click_url)?.click_url;
|
|
40
|
+
return {
|
|
41
|
+
status: 'filled',
|
|
42
|
+
message_id: ad.messageId,
|
|
43
|
+
sponsored_disclosure: exports.SPONSORED_DISCLOSURE,
|
|
44
|
+
ad: {
|
|
45
|
+
title: p.title,
|
|
46
|
+
subtitle: p.subtitle,
|
|
47
|
+
...(p.imageUrl ? { image_url: p.imageUrl } : {}),
|
|
48
|
+
...(p.altText ? { alt_text: p.altText } : {}),
|
|
49
|
+
actions,
|
|
50
|
+
},
|
|
51
|
+
suggested_text: [
|
|
52
|
+
`Sponsored: ${[p.title, p.subtitle].filter(Boolean).join(' — ') || p.altText || ''}`,
|
|
53
|
+
...(firstUrl ? [firstUrl] : []),
|
|
54
|
+
].join('\n'),
|
|
55
|
+
};
|
|
56
|
+
}
|
|
57
|
+
//# sourceMappingURL=llm.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"llm.js","sourceRoot":"","sources":["../../src/render/llm.ts"],"names":[],"mappings":";;;AAyDA,gCA2BC;AApFD,oCAAgE;AAEhE;;;;GAIG;AACU,QAAA,oBAAoB,GAC/B,sEAAsE;IACtE,wEAAwE;IACxE,mEAAmE;IACnE,wEAAwE;IACxE,sEAAsE;IACtE,iBAAiB,CAAC;AAgCpB;;;;;;;;;;;GAWG;AACH,SAAgB,UAAU,CAAC,EAA0B;IACnD,MAAM,CAAC,GAAG,EAAE,CAAC,OAAO,CAAC;IACrB,MAAM,OAAO,GAAmB,CAAC,CAAC,CAAC,OAAO,IAAI,EAAE,CAAC,CAAC,GAAG,CAAC,CAAC,CAAC,EAAE,CAAC,CAAC;QAC1D,KAAK,EAAE,CAAC,CAAC,IAAI;QACb,GAAG,CAAC,CAAC,CAAC,QAAQ,EAAE,GAAG,CAAC,CAAC,CAAC,EAAE,SAAS,EAAE,CAAC,CAAC,QAAQ,CAAC,GAAG,EAAE,CAAC,CAAC,CAAC,EAAE,CAAC;QACzD,GAAG,CAAC,CAAC,CAAC,IAAI,EAAE,WAAW,CAAC,CAAC,CAAC,EAAE,YAAY,EAAE,CAAC,CAAC,IAAI,CAAC,WAAW,EAAE,CAAC,CAAC,CAAC,EAAE,CAAC;QACpE,GAAG,CAAC,CAAC,CAAC,IAAI,KAAK,oBAAY,CAAC,IAAI,IAAI,CAAC,CAAC,OAAO;YAC3C,CAAC,CAAC,EAAE,aAAa,EAAE,CAAC,CAAC,OAAO,EAAE;YAC9B,CAAC,CAAC,EAAE,CAAC;KACR,CAAC,CAAC,CAAC;IACJ,MAAM,QAAQ,GAAG,OAAO,CAAC,IAAI,CAAC,CAAC,CAAC,EAAE,CAAC,CAAC,CAAC,SAAS,CAAC,EAAE,SAAS,CAAC;IAC3D,OAAO;QACL,MAAM,EAAE,QAAQ;QAChB,UAAU,EAAE,EAAE,CAAC,SAAS;QACxB,oBAAoB,EAAE,4BAAoB;QAC1C,EAAE,EAAE;YACF,KAAK,EAAE,CAAC,CAAC,KAAK;YACd,QAAQ,EAAE,CAAC,CAAC,QAAQ;YACpB,GAAG,CAAC,CAAC,CAAC,QAAQ,CAAC,CAAC,CAAC,EAAE,SAAS,EAAE,CAAC,CAAC,QAAQ,EAAE,CAAC,CAAC,CAAC,EAAE,CAAC;YAChD,GAAG,CAAC,CAAC,CAAC,OAAO,CAAC,CAAC,CAAC,EAAE,QAAQ,EAAE,CAAC,CAAC,OAAO,EAAE,CAAC,CAAC,CAAC,EAAE,CAAC;YAC7C,OAAO;SACR;QACD,cAAc,EAAE;YACd,cAAc,CAAC,CAAC,CAAC,KAAK,EAAE,CAAC,CAAC,QAAQ,CAAC,CAAC,MAAM,CAAC,OAAO,CAAC,CAAC,IAAI,CAAC,KAAK,CAAC,IAAI,CAAC,CAAC,OAAO,IAAI,EAAE,EAAE;YACpF,GAAG,CAAC,QAAQ,CAAC,CAAC,CAAC,CAAC,QAAQ,CAAC,CAAC,CAAC,CAAC,EAAE,CAAC;SAChC,CAAC,IAAI,CAAC,IAAI,CAAC;KACb,CAAC;AACJ,CAAC"}
|
package/bin/render/webchat.d.ts
CHANGED
|
@@ -23,6 +23,11 @@ export interface WebchatButton {
|
|
|
23
23
|
url?: string;
|
|
24
24
|
/** Phone number to dial (Call actions). */
|
|
25
25
|
phoneNumber?: string;
|
|
26
|
+
/**
|
|
27
|
+
* Postback data for a reply (Text actions). When the user taps the button,
|
|
28
|
+
* pass it to `AdChat.recordInteraction`.
|
|
29
|
+
*/
|
|
30
|
+
postbackData?: string;
|
|
26
31
|
/** Stable id for the button (the ad action id). */
|
|
27
32
|
id: string;
|
|
28
33
|
}
|
package/bin/render/webchat.js
CHANGED
|
@@ -26,6 +26,9 @@ function renderWebchat(payload) {
|
|
|
26
26
|
...(action.type === types_1.AdActionType.Call && action.call
|
|
27
27
|
? { phoneNumber: action.call.phoneNumber }
|
|
28
28
|
: {}),
|
|
29
|
+
...(action.type === types_1.AdActionType.Text
|
|
30
|
+
? { postbackData: action.context }
|
|
31
|
+
: {}),
|
|
29
32
|
id: action.actionId,
|
|
30
33
|
})),
|
|
31
34
|
disclosure: 'Sponsored',
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"webchat.js","sourceRoot":"","sources":["../../src/render/webchat.ts"],"names":[],"mappings":";;
|
|
1
|
+
{"version":3,"file":"webchat.js","sourceRoot":"","sources":["../../src/render/webchat.ts"],"names":[],"mappings":";;AA+CA,sCAoBC;AAnED,oCAA4D;AAoC5D;;;;;;;;;;GAUG;AACH,SAAgB,aAAa,CAAC,OAA2B;IACvD,MAAM,KAAK,GAAG,CAAC,OAAO,CAAC,KAAK,EAAE,OAAO,CAAC,QAAQ,CAAC,CAAC,MAAM,CAAC,OAAO,CAAC,CAAC;IAChE,OAAO;QACL,IAAI,EAAE,KAAK,CAAC,IAAI,CAAC,IAAI,CAAC,IAAI,OAAO,CAAC,OAAO,IAAI,EAAE;QAC/C,GAAG,CAAC,OAAO,CAAC,QAAQ,CAAC,CAAC,CAAC,EAAE,QAAQ,EAAE,OAAO,CAAC,QAAQ,EAAE,CAAC,CAAC,CAAC,EAAE,CAAC;QAC3D,OAAO,EAAE,CAAC,OAAO,CAAC,OAAO,IAAI,EAAE,CAAC,CAAC,GAAG,CAAC,MAAM,CAAC,EAAE,CAAC,CAAC;YAC9C,KAAK,EAAE,MAAM,CAAC,IAAI;YAClB,GAAG,CAAC,MAAM,CAAC,IAAI,KAAK,oBAAY,CAAC,QAAQ,IAAI,MAAM,CAAC,QAAQ;gBAC1D,CAAC,CAAC,EAAE,GAAG,EAAE,MAAM,CAAC,QAAQ,CAAC,GAAG,EAAE;gBAC9B,CAAC,CAAC,EAAE,CAAC;YACP,GAAG,CAAC,MAAM,CAAC,IAAI,KAAK,oBAAY,CAAC,IAAI,IAAI,MAAM,CAAC,IAAI;gBAClD,CAAC,CAAC,EAAE,WAAW,EAAE,MAAM,CAAC,IAAI,CAAC,WAAW,EAAE;gBAC1C,CAAC,CAAC,EAAE,CAAC;YACP,GAAG,CAAC,MAAM,CAAC,IAAI,KAAK,oBAAY,CAAC,IAAI;gBACnC,CAAC,CAAC,EAAE,YAAY,EAAE,MAAM,CAAC,OAAO,EAAE;gBAClC,CAAC,CAAC,EAAE,CAAC;YACP,EAAE,EAAE,MAAM,CAAC,QAAQ;SACpB,CAAC,CAAC;QACH,UAAU,EAAE,WAAW;KACxB,CAAC;AACJ,CAAC"}
|
package/bin/render/whatsapp.d.ts
CHANGED
|
@@ -26,9 +26,39 @@ export type WhatsAppMessage = {
|
|
|
26
26
|
* message object.
|
|
27
27
|
*
|
|
28
28
|
* Mapping (faithful to the platform's channel renderers):
|
|
29
|
-
* -
|
|
30
|
-
* title/subtitle body, "Sponsored" footer,
|
|
29
|
+
* - Text (reply) actions → interactive `button` message: image header,
|
|
30
|
+
* title/subtitle body (Navigate URLs appended), "Sponsored" footer, and
|
|
31
|
+
* up to three reply buttons whose id is the action's postback data. A tap
|
|
32
|
+
* comes back on your webhook as `interactive.button_reply.id`; pass it to
|
|
33
|
+
* `AdChat.recordInteraction` (see {@link whatsAppPostbackData}).
|
|
34
|
+
* - Otherwise one Navigate action → interactive `cta_url` message: image
|
|
35
|
+
* header, title/subtitle body, "Sponsored" footer, URL button
|
|
36
|
+
* (click-tracked).
|
|
31
37
|
* - Otherwise → image with caption (or plain text if no image); Navigate
|
|
32
38
|
* URLs are appended to the text, SMS-style.
|
|
39
|
+
*
|
|
40
|
+
* WhatsApp does not mix URL and reply buttons in one message, so an ad with
|
|
41
|
+
* both shows its replies as buttons and its links in the body.
|
|
33
42
|
*/
|
|
34
43
|
export declare function renderWhatsApp(payload: UniversalAdPayload): WhatsAppMessage;
|
|
44
|
+
/**
|
|
45
|
+
* The ad postback data in a WhatsApp Cloud API webhook message, or
|
|
46
|
+
* `undefined` when the message is not a tap on an ad's reply button.
|
|
47
|
+
* {@link renderWhatsApp} sets each reply button's id to the action's
|
|
48
|
+
* postback data; WhatsApp returns it as `interactive.button_reply.id`.
|
|
49
|
+
*
|
|
50
|
+
* ```ts
|
|
51
|
+
* // entry[].changes[].value.messages[]
|
|
52
|
+
* const postbackData = whatsAppPostbackData(message);
|
|
53
|
+
* if (postbackData) await adchat.recordInteraction(postbackData);
|
|
54
|
+
* ```
|
|
55
|
+
*
|
|
56
|
+
* https://developers.facebook.com/docs/whatsapp/cloud-api/webhooks/payload-examples
|
|
57
|
+
*/
|
|
58
|
+
export declare function whatsAppPostbackData(message: {
|
|
59
|
+
interactive?: {
|
|
60
|
+
button_reply?: {
|
|
61
|
+
id?: unknown;
|
|
62
|
+
};
|
|
63
|
+
};
|
|
64
|
+
}): string | undefined;
|
package/bin/render/whatsapp.js
CHANGED
|
@@ -1,22 +1,68 @@
|
|
|
1
1
|
"use strict";
|
|
2
2
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
3
|
exports.renderWhatsApp = renderWhatsApp;
|
|
4
|
+
exports.whatsAppPostbackData = whatsAppPostbackData;
|
|
5
|
+
const client_1 = require("../client");
|
|
4
6
|
const types_1 = require("../types");
|
|
7
|
+
/** WhatsApp Cloud API limits for interactive messages. */
|
|
8
|
+
const MAX_REPLY_BUTTONS = 3;
|
|
9
|
+
const MAX_BUTTON_TITLE = 20;
|
|
10
|
+
const MAX_BUTTON_ID = 256;
|
|
11
|
+
const MAX_BODY = 1024;
|
|
5
12
|
/**
|
|
6
13
|
* Render a universal ('direqt' format) ad payload as a WhatsApp Cloud API
|
|
7
14
|
* message object.
|
|
8
15
|
*
|
|
9
16
|
* Mapping (faithful to the platform's channel renderers):
|
|
10
|
-
* -
|
|
11
|
-
* title/subtitle body, "Sponsored" footer,
|
|
17
|
+
* - Text (reply) actions → interactive `button` message: image header,
|
|
18
|
+
* title/subtitle body (Navigate URLs appended), "Sponsored" footer, and
|
|
19
|
+
* up to three reply buttons whose id is the action's postback data. A tap
|
|
20
|
+
* comes back on your webhook as `interactive.button_reply.id`; pass it to
|
|
21
|
+
* `AdChat.recordInteraction` (see {@link whatsAppPostbackData}).
|
|
22
|
+
* - Otherwise one Navigate action → interactive `cta_url` message: image
|
|
23
|
+
* header, title/subtitle body, "Sponsored" footer, URL button
|
|
24
|
+
* (click-tracked).
|
|
12
25
|
* - Otherwise → image with caption (or plain text if no image); Navigate
|
|
13
26
|
* URLs are appended to the text, SMS-style.
|
|
27
|
+
*
|
|
28
|
+
* WhatsApp does not mix URL and reply buttons in one message, so an ad with
|
|
29
|
+
* both shows its replies as buttons and its links in the body.
|
|
14
30
|
*/
|
|
15
31
|
function renderWhatsApp(payload) {
|
|
16
32
|
const bodyText = [payload.title, payload.subtitle].filter(Boolean).join('\n') ||
|
|
17
33
|
payload.altText ||
|
|
18
34
|
'';
|
|
19
|
-
const
|
|
35
|
+
const actions = payload.actions || [];
|
|
36
|
+
const navigateActions = actions.filter(a => a.type === types_1.AdActionType.Navigate && a.navigate?.url);
|
|
37
|
+
const urls = navigateActions.map(a => a.navigate.url);
|
|
38
|
+
const header = payload.imageUrl
|
|
39
|
+
? { header: { type: 'image', image: { link: payload.imageUrl } } }
|
|
40
|
+
: {};
|
|
41
|
+
const replies = actions
|
|
42
|
+
.filter((a) => a.type === types_1.AdActionType.Text &&
|
|
43
|
+
!!a.text &&
|
|
44
|
+
!!a.context &&
|
|
45
|
+
a.context.length <= MAX_BUTTON_ID)
|
|
46
|
+
.slice(0, MAX_REPLY_BUTTONS);
|
|
47
|
+
if (replies.length) {
|
|
48
|
+
return {
|
|
49
|
+
messaging_product: 'whatsapp',
|
|
50
|
+
recipient_type: 'individual',
|
|
51
|
+
type: 'interactive',
|
|
52
|
+
interactive: {
|
|
53
|
+
type: 'button',
|
|
54
|
+
...header,
|
|
55
|
+
body: { text: truncate([bodyText, ...urls].join('\n'), MAX_BODY) },
|
|
56
|
+
footer: { text: 'Sponsored' },
|
|
57
|
+
action: {
|
|
58
|
+
buttons: replies.map(a => ({
|
|
59
|
+
type: 'reply',
|
|
60
|
+
reply: { id: a.context, title: truncate(a.text, MAX_BUTTON_TITLE) },
|
|
61
|
+
})),
|
|
62
|
+
},
|
|
63
|
+
},
|
|
64
|
+
};
|
|
65
|
+
}
|
|
20
66
|
if (navigateActions.length === 1) {
|
|
21
67
|
const action = navigateActions[0];
|
|
22
68
|
return {
|
|
@@ -25,15 +71,13 @@ function renderWhatsApp(payload) {
|
|
|
25
71
|
type: 'interactive',
|
|
26
72
|
interactive: {
|
|
27
73
|
type: 'cta_url',
|
|
28
|
-
...
|
|
29
|
-
? { header: { type: 'image', image: { link: payload.imageUrl } } }
|
|
30
|
-
: {}),
|
|
74
|
+
...header,
|
|
31
75
|
body: { text: bodyText },
|
|
32
76
|
footer: { text: 'Sponsored' },
|
|
33
77
|
action: {
|
|
34
78
|
name: 'cta_url',
|
|
35
79
|
parameters: {
|
|
36
|
-
display_text: truncate(action.text,
|
|
80
|
+
display_text: truncate(action.text, MAX_BUTTON_TITLE) || 'Learn more',
|
|
37
81
|
// Already a click-tracking URL that 302s to the advertiser.
|
|
38
82
|
url: action.navigate.url,
|
|
39
83
|
},
|
|
@@ -42,7 +86,6 @@ function renderWhatsApp(payload) {
|
|
|
42
86
|
};
|
|
43
87
|
}
|
|
44
88
|
// Fallback: degrade like the SMS renderer — text plus URL(s).
|
|
45
|
-
const urls = navigateActions.map(a => a.navigate.url);
|
|
46
89
|
const caption = ['Sponsored', bodyText, ...urls].filter(Boolean).join('\n');
|
|
47
90
|
if (payload.imageUrl) {
|
|
48
91
|
return {
|
|
@@ -59,7 +102,25 @@ function renderWhatsApp(payload) {
|
|
|
59
102
|
text: { body: caption, preview_url: true },
|
|
60
103
|
};
|
|
61
104
|
}
|
|
62
|
-
/**
|
|
105
|
+
/**
|
|
106
|
+
* The ad postback data in a WhatsApp Cloud API webhook message, or
|
|
107
|
+
* `undefined` when the message is not a tap on an ad's reply button.
|
|
108
|
+
* {@link renderWhatsApp} sets each reply button's id to the action's
|
|
109
|
+
* postback data; WhatsApp returns it as `interactive.button_reply.id`.
|
|
110
|
+
*
|
|
111
|
+
* ```ts
|
|
112
|
+
* // entry[].changes[].value.messages[]
|
|
113
|
+
* const postbackData = whatsAppPostbackData(message);
|
|
114
|
+
* if (postbackData) await adchat.recordInteraction(postbackData);
|
|
115
|
+
* ```
|
|
116
|
+
*
|
|
117
|
+
* https://developers.facebook.com/docs/whatsapp/cloud-api/webhooks/payload-examples
|
|
118
|
+
*/
|
|
119
|
+
function whatsAppPostbackData(message) {
|
|
120
|
+
const id = message?.interactive?.button_reply?.id;
|
|
121
|
+
return (0, client_1.isAdPostbackData)(id) ? id : undefined;
|
|
122
|
+
}
|
|
123
|
+
/** WhatsApp limits button titles to 20 characters and bodies to 1024. */
|
|
63
124
|
function truncate(s, max) {
|
|
64
125
|
if (!s)
|
|
65
126
|
return s;
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"whatsapp.js","sourceRoot":"","sources":["../../src/render/whatsapp.ts"],"names":[],"mappings":";;
|
|
1
|
+
{"version":3,"file":"whatsapp.js","sourceRoot":"","sources":["../../src/render/whatsapp.ts"],"names":[],"mappings":"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"}
|
package/bin/types.d.ts
CHANGED
|
@@ -21,10 +21,51 @@ export interface ConversationTurn {
|
|
|
21
21
|
* summary of the conversation so far, or the most recent turns.
|
|
22
22
|
*/
|
|
23
23
|
export type AdContext = string | ConversationTurn[];
|
|
24
|
+
/**
|
|
25
|
+
* A high-level blocklist: categories and/or advertisers a publisher never
|
|
26
|
+
* wants served into their conversations. Set it once on the {@link AdChat}
|
|
27
|
+
* client (applies to every fetch) and/or per {@link FetchAdParams} call
|
|
28
|
+
* (merged with the client-wide list).
|
|
29
|
+
*
|
|
30
|
+
* ```ts
|
|
31
|
+
* const adchat = new AdChat({
|
|
32
|
+
* fetchKey: process.env.ADCHAT_FETCH_KEY!,
|
|
33
|
+
* block: { categories: ['gambling', 'alcohol'], advertisers: ['AcmeCorp'] },
|
|
34
|
+
* });
|
|
35
|
+
* ```
|
|
36
|
+
*
|
|
37
|
+
* @remarks **Roadmap / beta.** Block lists are sent to the server on every
|
|
38
|
+
* fetch and are honored where server-side enforcement is available;
|
|
39
|
+
* category/advertiser enforcement is still rolling out, so treat this as a
|
|
40
|
+
* best-effort filter rather than a guarantee for now. The SDK surface and the
|
|
41
|
+
* wire contract are stable — enforcement is tracked as an `ads` follow-up.
|
|
42
|
+
*/
|
|
43
|
+
export interface BlockList {
|
|
44
|
+
/**
|
|
45
|
+
* Category codes/labels to block (e.g. `'gambling'`, `'alcohol'`,
|
|
46
|
+
* `'politics'`). Matched against each candidate ad's categories.
|
|
47
|
+
*/
|
|
48
|
+
categories?: string[];
|
|
49
|
+
/**
|
|
50
|
+
* Advertiser names/ids to block. Ads from these advertisers are excluded
|
|
51
|
+
* from selection.
|
|
52
|
+
*/
|
|
53
|
+
advertisers?: string[];
|
|
54
|
+
}
|
|
24
55
|
export interface FetchAdParams {
|
|
25
56
|
/**
|
|
26
|
-
*
|
|
27
|
-
*
|
|
57
|
+
* Id of the Application (agent) this fetch is for. Not needed with a fetch
|
|
58
|
+
* key, which is bound to one application. Required with any other key.
|
|
59
|
+
* When both are present they must agree. Overrides
|
|
60
|
+
* {@link AdChatOptions.application}.
|
|
61
|
+
*/
|
|
62
|
+
application?: string;
|
|
63
|
+
/**
|
|
64
|
+
* **Optional — most publishers can omit this.** Ad units are an *Offer
|
|
65
|
+
* Manager* concept for pinning a specific placement; a plain in-chat-ads
|
|
66
|
+
* integration doesn't need one. When omitted, the fetch fills against the
|
|
67
|
+
* application's default ad unit (created on first use). Advanced users pin
|
|
68
|
+
* a specific unit by code; codes are unique within an application.
|
|
28
69
|
*/
|
|
29
70
|
adUnit?: string;
|
|
30
71
|
/** Render format for the payload. Defaults to 'direqt' (universal JSON). */
|
|
@@ -41,6 +82,12 @@ export interface FetchAdParams {
|
|
|
41
82
|
context?: AdContext;
|
|
42
83
|
/** Targeting attributes (key/value pairs) for rule targeting. */
|
|
43
84
|
attributes?: Record<string, string>;
|
|
85
|
+
/**
|
|
86
|
+
* Block categories/advertisers for this fetch only. Merged with any
|
|
87
|
+
* client-wide {@link AdChatOptions.block}. See {@link BlockList} (roadmap /
|
|
88
|
+
* beta — enforcement is rolling out server-side).
|
|
89
|
+
*/
|
|
90
|
+
block?: BlockList;
|
|
44
91
|
/**
|
|
45
92
|
* Marks the fetch as a test: excluded from normal reporting and
|
|
46
93
|
* performance statistics.
|
|
@@ -50,7 +97,9 @@ export interface FetchAdParams {
|
|
|
50
97
|
/** Action types on a universal ad payload. */
|
|
51
98
|
export declare enum AdActionType {
|
|
52
99
|
Navigate = "Navigate",
|
|
53
|
-
Call = "Call"
|
|
100
|
+
Call = "Call",
|
|
101
|
+
/** A reply: the tap comes back to your agent with the postback data. */
|
|
102
|
+
Text = "Text"
|
|
54
103
|
}
|
|
55
104
|
/**
|
|
56
105
|
* A tappable action on an ad (button / suggested reply).
|
|
@@ -116,6 +165,46 @@ export interface Ad<P = unknown> {
|
|
|
116
165
|
media?: AdMedia[];
|
|
117
166
|
isTest?: boolean;
|
|
118
167
|
}
|
|
168
|
+
/**
|
|
169
|
+
* Reserved, always-fills sandbox ad unit. Fetching this ad unit returns a
|
|
170
|
+
* canned, clearly-labeled demo card regardless of account configuration —
|
|
171
|
+
* use it to verify your integration end-to-end before your real ad units
|
|
172
|
+
* have eligible line items. It records no billing or impression events.
|
|
173
|
+
*/
|
|
174
|
+
export declare const SANDBOX_AD_UNIT = "__sandbox__";
|
|
175
|
+
/**
|
|
176
|
+
* Machine-readable reason a fetch produced no ad. Returned by
|
|
177
|
+
* {@link AdChat.fetchAdDebug} so a legitimate empty result can be told apart
|
|
178
|
+
* from a misconfiguration:
|
|
179
|
+
*
|
|
180
|
+
* - `unknown_ad_unit` — no ad unit with that code exists for this org
|
|
181
|
+
* (typo, or the API key belongs to a different org). **Misconfiguration.**
|
|
182
|
+
* - `ad_unit_disabled` — the ad unit exists but is disabled.
|
|
183
|
+
* **Misconfiguration.**
|
|
184
|
+
* - `no_eligible_line_items` — the ad unit exists but has no line
|
|
185
|
+
* items configured, or none eligible for this request. **Misconfiguration**
|
|
186
|
+
* during onboarding (nothing to serve yet).
|
|
187
|
+
* - `frequency_capped` — this subscriber has hit the configured frequency
|
|
188
|
+
* cap. Normal.
|
|
189
|
+
* - `appropriateness_suppressed` — serving was suppressed because the
|
|
190
|
+
* conversational context was assessed as inappropriate for advertising.
|
|
191
|
+
* Normal.
|
|
192
|
+
* - `no_demand` — everything is configured correctly; there was simply no
|
|
193
|
+
* ad to serve right now. Normal (a legitimate no-fill).
|
|
194
|
+
*/
|
|
195
|
+
export type NoFillReason = 'unknown_ad_unit' | 'ad_unit_disabled' | 'no_eligible_line_items' | 'frequency_capped' | 'appropriateness_suppressed' | 'no_demand';
|
|
196
|
+
/**
|
|
197
|
+
* Result of {@link AdChat.fetchAdDebug}: either a served ad, or a no-fill
|
|
198
|
+
* with a machine-readable {@link NoFillReason} explaining why.
|
|
199
|
+
*/
|
|
200
|
+
export type FetchAdResult<P = unknown> = {
|
|
201
|
+
filled: true;
|
|
202
|
+
ad: Ad<P>;
|
|
203
|
+
} | {
|
|
204
|
+
filled: false;
|
|
205
|
+
reason: NoFillReason;
|
|
206
|
+
message: string;
|
|
207
|
+
};
|
|
119
208
|
/** Event types accepted by the v2 event API. */
|
|
120
209
|
export type AdEventType = 'send' | 'deliver' | 'read' | 'click' | 'conversion';
|
|
121
210
|
export interface RecordEventParams {
|
|
@@ -143,6 +232,19 @@ export interface RecordConversionParams {
|
|
|
143
232
|
data?: object;
|
|
144
233
|
subscriber?: string;
|
|
145
234
|
}
|
|
235
|
+
/**
|
|
236
|
+
* Result of {@link AdChat.recordInteraction}: the tapped action, and for a
|
|
237
|
+
* reply with an accept or decline role, the response recorded in the
|
|
238
|
+
* customer's history.
|
|
239
|
+
*/
|
|
240
|
+
export interface InteractionResult {
|
|
241
|
+
/** The tapped action's id. */
|
|
242
|
+
actionId: string;
|
|
243
|
+
/** The tapped reply's role, if it has one. */
|
|
244
|
+
role?: 'accept' | 'decline';
|
|
245
|
+
/** The response recorded in subscriber history, if any. */
|
|
246
|
+
recorded?: 'accepted' | 'declined';
|
|
247
|
+
}
|
|
146
248
|
export interface ClickUrlParams {
|
|
147
249
|
/** The `messageId` from the fetch response. */
|
|
148
250
|
messageId: string;
|
package/bin/types.js
CHANGED
|
@@ -6,13 +6,22 @@
|
|
|
6
6
|
* the `ads` service (`src/ads-api/v1`) and `ads/ads-api/src/v1/fetch.ts`.
|
|
7
7
|
*/
|
|
8
8
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
9
|
-
exports.AdChatApiError = exports.AdActionType = void 0;
|
|
9
|
+
exports.AdChatApiError = exports.SANDBOX_AD_UNIT = exports.AdActionType = void 0;
|
|
10
10
|
/** Action types on a universal ad payload. */
|
|
11
11
|
var AdActionType;
|
|
12
12
|
(function (AdActionType) {
|
|
13
13
|
AdActionType["Navigate"] = "Navigate";
|
|
14
14
|
AdActionType["Call"] = "Call";
|
|
15
|
+
/** A reply: the tap comes back to your agent with the postback data. */
|
|
16
|
+
AdActionType["Text"] = "Text";
|
|
15
17
|
})(AdActionType || (exports.AdActionType = AdActionType = {}));
|
|
18
|
+
/**
|
|
19
|
+
* Reserved, always-fills sandbox ad unit. Fetching this ad unit returns a
|
|
20
|
+
* canned, clearly-labeled demo card regardless of account configuration —
|
|
21
|
+
* use it to verify your integration end-to-end before your real ad units
|
|
22
|
+
* have eligible line items. It records no billing or impression events.
|
|
23
|
+
*/
|
|
24
|
+
exports.SANDBOX_AD_UNIT = '__sandbox__';
|
|
16
25
|
/**
|
|
17
26
|
* Error thrown for non-2xx API responses.
|
|
18
27
|
*
|
package/bin/types.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"types.js","sourceRoot":"","sources":["../src/types.ts"],"names":[],"mappings":";AAAA;;;;;GAKG;;;
|
|
1
|
+
{"version":3,"file":"types.js","sourceRoot":"","sources":["../src/types.ts"],"names":[],"mappings":";AAAA;;;;;GAKG;;;AAyGH,8CAA8C;AAC9C,IAAY,YAKX;AALD,WAAY,YAAY;IACtB,qCAAqB,CAAA;IACrB,6BAAa,CAAA;IACb,wEAAwE;IACxE,6BAAa,CAAA;AACf,CAAC,EALW,YAAY,4BAAZ,YAAY,QAKvB;AAmED;;;;;GAKG;AACU,QAAA,eAAe,GAAG,aAAa,CAAC;AA2F7C;;;;;GAKG;AACH,MAAa,cAAe,SAAQ,KAAK;IAGrB;IAKA;IAPlB;IACE,uCAAuC;IACvB,MAAc;IAC9B;;;OAGG;IACa,MAAe,EAC/B,OAAgB;QAEhB,KAAK,CAAC,OAAO,IAAI,8BAA8B,MAAM,IAAI,MAAM,IAAI,EAAE,EAAE,CAAC,CAAC;QARzD,WAAM,GAAN,MAAM,CAAQ;QAKd,WAAM,GAAN,MAAM,CAAS;QAI/B,IAAI,CAAC,IAAI,GAAG,gBAAgB,CAAC;IAC/B,CAAC;CACF;AAdD,wCAcC"}
|
package/package.json
CHANGED