@adzenai/ai 1.1.0 → 1.2.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 +51 -3
- package/dist/copilotkit/index.cjs +499 -66
- package/dist/copilotkit/index.cjs.map +1 -1
- package/dist/copilotkit/index.d.cts +56 -8
- package/dist/copilotkit/index.d.ts +56 -8
- package/dist/copilotkit/index.js +498 -65
- package/dist/copilotkit/index.js.map +1 -1
- package/dist/copilotkit/react/index.cjs +222 -42
- package/dist/copilotkit/react/index.cjs.map +1 -1
- package/dist/copilotkit/react/index.d.cts +64 -10
- package/dist/copilotkit/react/index.d.ts +64 -10
- package/dist/copilotkit/react/index.js +218 -42
- package/dist/copilotkit/react/index.js.map +1 -1
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.cts +1 -1
- package/dist/index.d.ts +1 -1
- package/package.json +3 -1
|
@@ -21,11 +21,16 @@ var __toCommonJS = (mod) => __copyProps(__defProp({}, "__esModule", { value: tru
|
|
|
21
21
|
var copilotkit_exports = {};
|
|
22
22
|
__export(copilotkit_exports, {
|
|
23
23
|
AdzenAsyncMiddleware: () => AdzenAsyncMiddleware,
|
|
24
|
-
|
|
24
|
+
AdzenStreamMiddleware: () => AdzenStreamMiddleware
|
|
25
25
|
});
|
|
26
26
|
module.exports = __toCommonJS(copilotkit_exports);
|
|
27
27
|
|
|
28
28
|
// src/copilotkit/middleware.ts
|
|
29
|
+
function generateIdempotencyKey() {
|
|
30
|
+
const globalCrypto = globalThis.crypto;
|
|
31
|
+
if (globalCrypto?.randomUUID) return globalCrypto.randomUUID();
|
|
32
|
+
return `adzen-${Date.now()}-${Math.random().toString(36).slice(2)}`;
|
|
33
|
+
}
|
|
29
34
|
function isFinalOutputEvent(event) {
|
|
30
35
|
const type = String(event.type ?? "");
|
|
31
36
|
if (type === "RUN_FINISHED" || type === "RunFinished") return false;
|
|
@@ -55,13 +60,15 @@ var AdzenAsyncMiddleware = class {
|
|
|
55
60
|
fetchedIds = /* @__PURE__ */ new Set();
|
|
56
61
|
pendingFetches = [];
|
|
57
62
|
fallbackCounter = 0;
|
|
63
|
+
conversationId;
|
|
58
64
|
constructor(config) {
|
|
59
65
|
this.config = {
|
|
60
66
|
...config,
|
|
61
|
-
endpointUrl: config.endpointUrl ?? "https://api.adzen.ai/v1
|
|
67
|
+
endpointUrl: config.endpointUrl ?? "https://api.adzen.ai/v1",
|
|
62
68
|
timeoutMs: config.timeoutMs ?? 3e3,
|
|
63
69
|
adUnitPosition: config.adUnitPosition ?? "chin"
|
|
64
70
|
};
|
|
71
|
+
this.conversationId = config.conversationId;
|
|
65
72
|
}
|
|
66
73
|
/**
|
|
67
74
|
* Process a single AG-UI event. Returns an array of events to emit
|
|
@@ -71,6 +78,12 @@ var AdzenAsyncMiddleware = class {
|
|
|
71
78
|
async processEvent(event) {
|
|
72
79
|
const type = String(event.type ?? "");
|
|
73
80
|
switch (type) {
|
|
81
|
+
case "RUN_STARTED":
|
|
82
|
+
case "RunStarted": {
|
|
83
|
+
const threadId = String(event.threadId ?? "");
|
|
84
|
+
if (threadId) this.conversationId = threadId;
|
|
85
|
+
return [event];
|
|
86
|
+
}
|
|
74
87
|
case "TEXT_MESSAGE_START":
|
|
75
88
|
case "TextMessageStart": {
|
|
76
89
|
const messageId = this.resolveMessageId(event);
|
|
@@ -158,31 +171,58 @@ var AdzenAsyncMiddleware = class {
|
|
|
158
171
|
const controller = new AbortController();
|
|
159
172
|
const timeout = setTimeout(() => controller.abort(), this.config.timeoutMs);
|
|
160
173
|
try {
|
|
174
|
+
const body = {
|
|
175
|
+
content,
|
|
176
|
+
message_id: messageId
|
|
177
|
+
};
|
|
178
|
+
if (this.config.location) body.location = this.config.location;
|
|
179
|
+
if (this.conversationId) body.conversation_id = this.conversationId;
|
|
180
|
+
const headers = {
|
|
181
|
+
"Content-Type": "application/json",
|
|
182
|
+
"X-API-Key": this.config.apiKey,
|
|
183
|
+
// AI-API requires an Idempotency-Key on /process (400 otherwise).
|
|
184
|
+
// One per ad fetch — each completed message is a distinct request.
|
|
185
|
+
"Idempotency-Key": generateIdempotencyKey()
|
|
186
|
+
};
|
|
187
|
+
if (this.config.profileId) headers["X-Profile-Id"] = this.config.profileId;
|
|
161
188
|
const res = await fetch(`${this.config.endpointUrl}/process`, {
|
|
162
189
|
method: "POST",
|
|
163
|
-
headers
|
|
164
|
-
|
|
165
|
-
"X-API-Key": this.config.apiKey
|
|
166
|
-
},
|
|
167
|
-
body: JSON.stringify({
|
|
168
|
-
content,
|
|
169
|
-
message_id: messageId
|
|
170
|
-
}),
|
|
190
|
+
headers,
|
|
191
|
+
body: JSON.stringify(body),
|
|
171
192
|
signal: controller.signal
|
|
172
193
|
});
|
|
173
194
|
if (!res.ok) return null;
|
|
174
195
|
const data = await res.json();
|
|
175
|
-
|
|
176
|
-
return null;
|
|
196
|
+
const first = data.ads?.[0];
|
|
197
|
+
if (!first) return null;
|
|
198
|
+
return this.mapResponseToPlacement(first);
|
|
177
199
|
} catch {
|
|
178
200
|
return null;
|
|
179
201
|
} finally {
|
|
180
202
|
clearTimeout(timeout);
|
|
181
203
|
}
|
|
182
204
|
}
|
|
205
|
+
mapResponseToPlacement(ad) {
|
|
206
|
+
return {
|
|
207
|
+
ad_id: String(ad.ad_id),
|
|
208
|
+
advertiser_name: ad.advertiser_name,
|
|
209
|
+
advertiser_image_url: ad.sponsor_logo_url || void 0,
|
|
210
|
+
headline: ad.title,
|
|
211
|
+
description: ad.description,
|
|
212
|
+
cta_text: ad.cta,
|
|
213
|
+
destination_url: ad.click_through_url,
|
|
214
|
+
creative_url: ad.creative_url ?? void 0,
|
|
215
|
+
adUnitPosition: ad.placement ?? this.config.adUnitPosition,
|
|
216
|
+
render_impression_url: ad.render_impression_url,
|
|
217
|
+
view_impression_url: ad.view_impression_url
|
|
218
|
+
};
|
|
219
|
+
}
|
|
183
220
|
makePlacementEvent(placement, messageId) {
|
|
184
221
|
return {
|
|
185
|
-
|
|
222
|
+
// AG-UI's event schema only accepts "CUSTOM"; @ag-ui/client zod-validates
|
|
223
|
+
// every SSE frame and aborts the run on an unknown type. "CUSTOM_EVENT"
|
|
224
|
+
// was rejected downstream.
|
|
225
|
+
type: "CUSTOM",
|
|
186
226
|
name: "adzen_placement",
|
|
187
227
|
messageId,
|
|
188
228
|
value: {
|
|
@@ -193,72 +233,465 @@ var AdzenAsyncMiddleware = class {
|
|
|
193
233
|
}
|
|
194
234
|
};
|
|
195
235
|
|
|
196
|
-
// src/copilotkit/
|
|
197
|
-
var
|
|
198
|
-
|
|
199
|
-
|
|
200
|
-
|
|
201
|
-
|
|
202
|
-
|
|
203
|
-
|
|
204
|
-
|
|
205
|
-
|
|
206
|
-
|
|
207
|
-
|
|
208
|
-
|
|
209
|
-
|
|
210
|
-
|
|
211
|
-
|
|
236
|
+
// src/copilotkit/AdzenStreamClient.ts
|
|
237
|
+
var import_core = require("@adzenai/core");
|
|
238
|
+
|
|
239
|
+
// src/copilotkit/parseSSE.ts
|
|
240
|
+
async function* parseSSE(stream) {
|
|
241
|
+
const reader = stream.getReader();
|
|
242
|
+
const decoder = new TextDecoder();
|
|
243
|
+
let buffer = "";
|
|
244
|
+
let currentEvent;
|
|
245
|
+
let dataLines = [];
|
|
246
|
+
try {
|
|
247
|
+
while (true) {
|
|
248
|
+
const { done, value } = await reader.read();
|
|
249
|
+
if (done) break;
|
|
250
|
+
buffer += decoder.decode(value, { stream: true });
|
|
251
|
+
const lines = buffer.split("\n");
|
|
252
|
+
buffer = lines.pop();
|
|
253
|
+
for (const raw of lines) {
|
|
254
|
+
const line = raw.endsWith("\r") ? raw.slice(0, -1) : raw;
|
|
255
|
+
if (line === "") {
|
|
256
|
+
if (dataLines.length > 0) {
|
|
257
|
+
yield { event: currentEvent, data: dataLines.join("\n") };
|
|
258
|
+
dataLines = [];
|
|
259
|
+
currentEvent = void 0;
|
|
260
|
+
}
|
|
261
|
+
continue;
|
|
262
|
+
}
|
|
263
|
+
if (line.startsWith(":")) continue;
|
|
264
|
+
const colonIdx = line.indexOf(":");
|
|
265
|
+
if (colonIdx === -1) continue;
|
|
266
|
+
const field = line.slice(0, colonIdx);
|
|
267
|
+
const value2 = line.slice(colonIdx + 1).replace(/^ /, "");
|
|
268
|
+
switch (field) {
|
|
269
|
+
case "data":
|
|
270
|
+
dataLines.push(value2);
|
|
271
|
+
break;
|
|
272
|
+
case "event":
|
|
273
|
+
currentEvent = value2;
|
|
274
|
+
break;
|
|
275
|
+
}
|
|
276
|
+
}
|
|
212
277
|
}
|
|
213
|
-
|
|
214
|
-
|
|
215
|
-
body = await request.json();
|
|
216
|
-
} catch {
|
|
217
|
-
return new Response(JSON.stringify({ error: "Invalid JSON" }), {
|
|
218
|
-
status: 400,
|
|
219
|
-
headers: { "Content-Type": "application/json" }
|
|
220
|
-
});
|
|
278
|
+
if (dataLines.length > 0) {
|
|
279
|
+
yield { event: currentEvent, data: dataLines.join("\n") };
|
|
221
280
|
}
|
|
222
|
-
|
|
223
|
-
|
|
224
|
-
|
|
225
|
-
|
|
226
|
-
|
|
227
|
-
|
|
281
|
+
} finally {
|
|
282
|
+
reader.releaseLock();
|
|
283
|
+
}
|
|
284
|
+
}
|
|
285
|
+
|
|
286
|
+
// src/copilotkit/AdzenStreamClient.ts
|
|
287
|
+
var DEFAULT_ENDPOINT = "https://api.adzen.ai/v1";
|
|
288
|
+
var DEFAULT_TIMEOUT_MS = 3e3;
|
|
289
|
+
var DEFAULT_POLL_INTERVAL_MS = 2e3;
|
|
290
|
+
var SUPPORTS_REQUEST_STREAMS = (() => {
|
|
291
|
+
try {
|
|
292
|
+
let duplexAccessed = false;
|
|
293
|
+
const hasContentType = new Request("http://localhost", {
|
|
294
|
+
method: "POST",
|
|
295
|
+
body: new ReadableStream(),
|
|
296
|
+
get duplex() {
|
|
297
|
+
duplexAccessed = true;
|
|
298
|
+
return "half";
|
|
228
299
|
}
|
|
300
|
+
}).headers.has("Content-Type");
|
|
301
|
+
return duplexAccessed && !hasContentType;
|
|
302
|
+
} catch {
|
|
303
|
+
return false;
|
|
304
|
+
}
|
|
305
|
+
})();
|
|
306
|
+
var AdzenStreamClient = class {
|
|
307
|
+
endpointUrl;
|
|
308
|
+
apiKey;
|
|
309
|
+
timeoutMs;
|
|
310
|
+
location;
|
|
311
|
+
profileId;
|
|
312
|
+
placementPollIntervalMs;
|
|
313
|
+
prefetchEnabled;
|
|
314
|
+
sidecarEnabled;
|
|
315
|
+
conversationId;
|
|
316
|
+
abortController = null;
|
|
317
|
+
placementEndpoint = null;
|
|
318
|
+
pollTimer = null;
|
|
319
|
+
streamEnded = false;
|
|
320
|
+
inlineAdQueue = [];
|
|
321
|
+
streamPromise = null;
|
|
322
|
+
prefetchPromise = null;
|
|
323
|
+
onInlineAd;
|
|
324
|
+
onPlacement;
|
|
325
|
+
onError;
|
|
326
|
+
onStreamStart;
|
|
327
|
+
onStreamEnd;
|
|
328
|
+
constructor(config) {
|
|
329
|
+
this.endpointUrl = config.endpointUrl ?? DEFAULT_ENDPOINT;
|
|
330
|
+
this.apiKey = config.apiKey;
|
|
331
|
+
this.timeoutMs = config.timeoutMs ?? DEFAULT_TIMEOUT_MS;
|
|
332
|
+
this.location = config.location;
|
|
333
|
+
this.profileId = config.profileId;
|
|
334
|
+
this.placementPollIntervalMs = config.placementPollIntervalMs ?? DEFAULT_POLL_INTERVAL_MS;
|
|
335
|
+
this.prefetchEnabled = config.prefetch ?? false;
|
|
336
|
+
this.sidecarEnabled = config.sidecar ?? true;
|
|
337
|
+
this.conversationId = config.conversationId ?? (0, import_core.uuidv4)();
|
|
338
|
+
this.onInlineAd = config.onInlineAd;
|
|
339
|
+
this.onPlacement = config.onPlacement;
|
|
340
|
+
this.onError = config.onError;
|
|
341
|
+
this.onStreamStart = config.onStreamStart;
|
|
342
|
+
this.onStreamEnd = config.onStreamEnd;
|
|
343
|
+
}
|
|
344
|
+
getConversationId() {
|
|
345
|
+
return this.conversationId;
|
|
346
|
+
}
|
|
347
|
+
setConversationId(id) {
|
|
348
|
+
this.conversationId = id;
|
|
349
|
+
}
|
|
350
|
+
/**
|
|
351
|
+
* Dispatches the prefetch without waiting for it.
|
|
352
|
+
*
|
|
353
|
+
* The request leaves before `connect()` opens `/stream`, which is all the
|
|
354
|
+
* 2-step chained flow needs — the two calls are correlated server-side by
|
|
355
|
+
* `conversation_id`. Awaiting the response here would stall the assistant
|
|
356
|
+
* message behind a full ad-matching round trip while the LLM keeps
|
|
357
|
+
* streaming, so the buffered tokens would then arrive in one burst.
|
|
358
|
+
*/
|
|
359
|
+
startPrefetch(prompt) {
|
|
360
|
+
if (!this.prefetchEnabled) return;
|
|
361
|
+
this.prefetchPromise = this.prefetchProcess(prompt);
|
|
362
|
+
}
|
|
363
|
+
/**
|
|
364
|
+
* 2-step chained flow: POST /process with the user prompt and
|
|
365
|
+
* conversationId. Awaits the response (or timeout) but discards the
|
|
366
|
+
* result. If it fails, logs and continues — the stream will proceed
|
|
367
|
+
* in degraded mode.
|
|
368
|
+
*/
|
|
369
|
+
async prefetchProcess(prompt) {
|
|
370
|
+
if (!this.prefetchEnabled) return;
|
|
371
|
+
const controller = new AbortController();
|
|
372
|
+
const timeout = setTimeout(() => controller.abort(), this.timeoutMs);
|
|
373
|
+
try {
|
|
374
|
+
const body = {
|
|
375
|
+
content: prompt,
|
|
376
|
+
message_id: (0, import_core.uuidv4)(),
|
|
377
|
+
conversation_id: this.conversationId
|
|
378
|
+
};
|
|
379
|
+
if (this.location) body.location = this.location;
|
|
380
|
+
const headers = {
|
|
381
|
+
"Content-Type": "application/json",
|
|
382
|
+
"X-API-Key": this.apiKey,
|
|
383
|
+
"Idempotency-Key": (0, import_core.uuidv4)()
|
|
384
|
+
};
|
|
385
|
+
if (this.profileId) headers["X-Profile-Id"] = this.profileId;
|
|
386
|
+
await fetch(`${this.endpointUrl}/process`, {
|
|
387
|
+
method: "POST",
|
|
388
|
+
headers,
|
|
389
|
+
body: JSON.stringify(body),
|
|
390
|
+
signal: controller.signal
|
|
391
|
+
});
|
|
392
|
+
} catch {
|
|
393
|
+
} finally {
|
|
394
|
+
clearTimeout(timeout);
|
|
229
395
|
}
|
|
230
|
-
|
|
231
|
-
|
|
232
|
-
|
|
233
|
-
|
|
234
|
-
|
|
396
|
+
}
|
|
397
|
+
/**
|
|
398
|
+
* Opens a streaming POST to /stream, piping the upstream LLM response
|
|
399
|
+
* body directly. The API reads the raw LLM stream in real time and
|
|
400
|
+
* sends SSE ad events back on the response side.
|
|
401
|
+
*
|
|
402
|
+
* @param messageId Unique ID for this assistant message
|
|
403
|
+
* @param body The upstream LLM response body (one branch of a tee)
|
|
404
|
+
*/
|
|
405
|
+
connect(messageId, body) {
|
|
406
|
+
this.streamEnded = false;
|
|
407
|
+
this.inlineAdQueue = [];
|
|
408
|
+
this.placementEndpoint = null;
|
|
409
|
+
this.streamPromise = null;
|
|
410
|
+
this.abortController = new AbortController();
|
|
411
|
+
if (!SUPPORTS_REQUEST_STREAMS) {
|
|
412
|
+
this.streamEnded = true;
|
|
413
|
+
this.onError?.({
|
|
414
|
+
type: "adzen_error",
|
|
415
|
+
code: "request_streams_unsupported",
|
|
416
|
+
message: "This runtime cannot send a ReadableStream as a request body, so the LLM stream cannot be proxied. Run AdzenStreamMiddleware server-side (Node 18+) \u2014 browsers other than Chromium do not support fetch upload streams."
|
|
417
|
+
});
|
|
418
|
+
void body.cancel().catch(() => {
|
|
419
|
+
});
|
|
420
|
+
return;
|
|
235
421
|
}
|
|
236
|
-
const
|
|
237
|
-
|
|
422
|
+
const headers = {
|
|
423
|
+
"Content-Type": "application/octet-stream",
|
|
424
|
+
"X-API-Key": this.apiKey,
|
|
425
|
+
"Idempotency-Key": (0, import_core.uuidv4)(),
|
|
426
|
+
"X-Message-Id": messageId,
|
|
427
|
+
"X-Conversation-Id": this.conversationId
|
|
428
|
+
};
|
|
429
|
+
if (this.profileId) headers["X-Profile-Id"] = this.profileId;
|
|
430
|
+
if (this.location) headers["X-Location"] = this.location;
|
|
431
|
+
this.streamPromise = this.executeStreamFetch(body, headers);
|
|
432
|
+
}
|
|
433
|
+
/**
|
|
434
|
+
* Waits for the SSE response stream from the API to finish, along with any
|
|
435
|
+
* prefetch still in flight. Neither can reject — both swallow their errors.
|
|
436
|
+
*/
|
|
437
|
+
async finalize() {
|
|
438
|
+
await Promise.all([this.prefetchPromise, this.streamPromise]);
|
|
439
|
+
}
|
|
440
|
+
async executeStreamFetch(body, headers) {
|
|
441
|
+
const timeout = setTimeout(
|
|
442
|
+
() => this.abortController?.abort(),
|
|
443
|
+
this.timeoutMs
|
|
444
|
+
);
|
|
238
445
|
try {
|
|
239
|
-
const
|
|
446
|
+
const res = await fetch(`${this.endpointUrl}/stream`, {
|
|
240
447
|
method: "POST",
|
|
241
|
-
headers
|
|
242
|
-
|
|
243
|
-
|
|
244
|
-
|
|
245
|
-
|
|
448
|
+
headers,
|
|
449
|
+
body,
|
|
450
|
+
signal: this.abortController?.signal,
|
|
451
|
+
// @ts-expect-error -- required for streaming request bodies (Node/undici)
|
|
452
|
+
duplex: "half"
|
|
246
453
|
});
|
|
247
|
-
|
|
248
|
-
|
|
249
|
-
|
|
454
|
+
clearTimeout(timeout);
|
|
455
|
+
if (!res.ok || !res.body) return;
|
|
456
|
+
await this.readStream(res.body);
|
|
457
|
+
} catch {
|
|
458
|
+
clearTimeout(timeout);
|
|
459
|
+
}
|
|
460
|
+
}
|
|
461
|
+
/** Drains and returns any queued inline ad events. */
|
|
462
|
+
drainInlineAds() {
|
|
463
|
+
const ads = this.inlineAdQueue.splice(0);
|
|
464
|
+
return ads;
|
|
465
|
+
}
|
|
466
|
+
/** Cancels pending requests and cleans up state. */
|
|
467
|
+
abort() {
|
|
468
|
+
this.abortController?.abort();
|
|
469
|
+
this.stopPolling();
|
|
470
|
+
this.streamEnded = true;
|
|
471
|
+
}
|
|
472
|
+
/** Polls the placement endpoint once. Called on an interval internally. */
|
|
473
|
+
async pollPlacements() {
|
|
474
|
+
if (!this.sidecarEnabled || !this.placementEndpoint) return;
|
|
475
|
+
try {
|
|
476
|
+
const res = await fetch(this.placementEndpoint, {
|
|
477
|
+
method: "GET",
|
|
478
|
+
headers: { "X-API-Key": this.apiKey }
|
|
250
479
|
});
|
|
480
|
+
if (!res.ok) return;
|
|
481
|
+
const data = await res.json();
|
|
482
|
+
for (const placement of data.placements) {
|
|
483
|
+
this.onPlacement?.(placement);
|
|
484
|
+
}
|
|
251
485
|
} catch {
|
|
252
|
-
return new Response(
|
|
253
|
-
JSON.stringify({ error: "Upstream request failed" }),
|
|
254
|
-
{ status: 502, headers: { "Content-Type": "application/json" } }
|
|
255
|
-
);
|
|
256
486
|
}
|
|
257
|
-
}
|
|
258
|
-
|
|
487
|
+
}
|
|
488
|
+
async readStream(body) {
|
|
489
|
+
try {
|
|
490
|
+
for await (const frame of parseSSE(body)) {
|
|
491
|
+
if (this.abortController?.signal.aborted) break;
|
|
492
|
+
this.handleFrame(frame.event, frame.data);
|
|
493
|
+
}
|
|
494
|
+
} catch {
|
|
495
|
+
} finally {
|
|
496
|
+
this.streamEnded = true;
|
|
497
|
+
this.stopPolling();
|
|
498
|
+
}
|
|
499
|
+
}
|
|
500
|
+
handleFrame(event, data) {
|
|
501
|
+
switch (event) {
|
|
502
|
+
case "adzen_stream_start": {
|
|
503
|
+
try {
|
|
504
|
+
const parsed = JSON.parse(data);
|
|
505
|
+
const evt = { type: "adzen_stream_start", ...parsed };
|
|
506
|
+
this.placementEndpoint = parsed.placement_endpoint;
|
|
507
|
+
this.onStreamStart?.(evt);
|
|
508
|
+
this.startPolling();
|
|
509
|
+
} catch {
|
|
510
|
+
}
|
|
511
|
+
break;
|
|
512
|
+
}
|
|
513
|
+
case "adzen_inline": {
|
|
514
|
+
try {
|
|
515
|
+
const ad = JSON.parse(data);
|
|
516
|
+
this.inlineAdQueue.push(ad);
|
|
517
|
+
this.onInlineAd?.(ad);
|
|
518
|
+
} catch {
|
|
519
|
+
}
|
|
520
|
+
break;
|
|
521
|
+
}
|
|
522
|
+
case "adzen_error": {
|
|
523
|
+
try {
|
|
524
|
+
const parsed = JSON.parse(data);
|
|
525
|
+
this.onError?.({ type: "adzen_error", ...parsed });
|
|
526
|
+
} catch {
|
|
527
|
+
}
|
|
528
|
+
break;
|
|
529
|
+
}
|
|
530
|
+
case "adzen_stream_end": {
|
|
531
|
+
try {
|
|
532
|
+
const parsed = JSON.parse(data);
|
|
533
|
+
const evt = { type: "adzen_stream_end", ...parsed };
|
|
534
|
+
this.onStreamEnd?.(evt);
|
|
535
|
+
} catch {
|
|
536
|
+
}
|
|
537
|
+
this.streamEnded = true;
|
|
538
|
+
this.stopPolling();
|
|
539
|
+
break;
|
|
540
|
+
}
|
|
541
|
+
default: {
|
|
542
|
+
if (event?.startsWith("adzen_debug_")) break;
|
|
543
|
+
break;
|
|
544
|
+
}
|
|
545
|
+
}
|
|
546
|
+
}
|
|
547
|
+
startPolling() {
|
|
548
|
+
if (!this.sidecarEnabled) return;
|
|
549
|
+
if (this.pollTimer || !this.placementEndpoint) return;
|
|
550
|
+
this.pollTimer = setInterval(
|
|
551
|
+
() => this.pollPlacements(),
|
|
552
|
+
this.placementPollIntervalMs
|
|
553
|
+
);
|
|
554
|
+
}
|
|
555
|
+
stopPolling() {
|
|
556
|
+
if (this.pollTimer) {
|
|
557
|
+
clearInterval(this.pollTimer);
|
|
558
|
+
this.pollTimer = null;
|
|
559
|
+
}
|
|
560
|
+
}
|
|
561
|
+
};
|
|
562
|
+
|
|
563
|
+
// src/copilotkit/streamMiddleware.ts
|
|
564
|
+
var AdzenStreamMiddleware = class {
|
|
565
|
+
config;
|
|
566
|
+
adUnitPosition;
|
|
567
|
+
constructor(config) {
|
|
568
|
+
this.config = config;
|
|
569
|
+
this.adUnitPosition = config.adUnitPosition ?? "chin";
|
|
570
|
+
}
|
|
571
|
+
async *processStream(events, options) {
|
|
572
|
+
const sidecarPlacements = [];
|
|
573
|
+
let client = null;
|
|
574
|
+
let currentMessageId = "";
|
|
575
|
+
let contentOffset = 0;
|
|
576
|
+
const onPlacement = (record) => {
|
|
577
|
+
sidecarPlacements.push(this.mapPlacementRecord(record, currentMessageId));
|
|
578
|
+
};
|
|
579
|
+
const onError = (error) => {
|
|
580
|
+
console.warn(`[adzen-stream] ${error.code}: ${error.message}`);
|
|
581
|
+
};
|
|
582
|
+
for await (const event of events) {
|
|
583
|
+
const type = String(event.type ?? "");
|
|
584
|
+
switch (type) {
|
|
585
|
+
case "RUN_STARTED":
|
|
586
|
+
case "RunStarted": {
|
|
587
|
+
const threadId = String(event.threadId ?? "");
|
|
588
|
+
client = new AdzenStreamClient({
|
|
589
|
+
...this.config,
|
|
590
|
+
conversationId: threadId || this.config.conversationId,
|
|
591
|
+
onPlacement,
|
|
592
|
+
onError
|
|
593
|
+
});
|
|
594
|
+
if (this.config.prefetch && options?.prompt) {
|
|
595
|
+
client.startPrefetch(options.prompt);
|
|
596
|
+
}
|
|
597
|
+
yield event;
|
|
598
|
+
break;
|
|
599
|
+
}
|
|
600
|
+
case "TEXT_MESSAGE_START":
|
|
601
|
+
case "TextMessageStart": {
|
|
602
|
+
currentMessageId = String(event.messageId ?? "");
|
|
603
|
+
contentOffset = 0;
|
|
604
|
+
if (client && options?.upstreamBody) {
|
|
605
|
+
client.connect(currentMessageId, options.upstreamBody);
|
|
606
|
+
}
|
|
607
|
+
yield event;
|
|
608
|
+
break;
|
|
609
|
+
}
|
|
610
|
+
case "TEXT_MESSAGE_CONTENT":
|
|
611
|
+
case "TextMessageContent": {
|
|
612
|
+
yield event;
|
|
613
|
+
contentOffset += String(event.delta ?? "").length;
|
|
614
|
+
if (client) {
|
|
615
|
+
for (const ad of client.drainInlineAds()) {
|
|
616
|
+
yield this.makeInlineAdEvent(ad, currentMessageId, contentOffset);
|
|
617
|
+
}
|
|
618
|
+
}
|
|
619
|
+
break;
|
|
620
|
+
}
|
|
621
|
+
case "TEXT_MESSAGE_END":
|
|
622
|
+
case "TextMessageEnd": {
|
|
623
|
+
if (client) {
|
|
624
|
+
await client.finalize();
|
|
625
|
+
for (const ad of client.drainInlineAds()) {
|
|
626
|
+
yield this.makeInlineAdEvent(ad, currentMessageId, contentOffset);
|
|
627
|
+
}
|
|
628
|
+
}
|
|
629
|
+
yield event;
|
|
630
|
+
break;
|
|
631
|
+
}
|
|
632
|
+
case "RUN_FINISHED":
|
|
633
|
+
case "RunFinished": {
|
|
634
|
+
if (client) {
|
|
635
|
+
await client.pollPlacements();
|
|
636
|
+
client.abort();
|
|
637
|
+
}
|
|
638
|
+
for (const placement of sidecarPlacements) {
|
|
639
|
+
yield this.makePlacementEvent(placement, placement.ad_id);
|
|
640
|
+
}
|
|
641
|
+
yield event;
|
|
642
|
+
break;
|
|
643
|
+
}
|
|
644
|
+
default:
|
|
645
|
+
yield event;
|
|
646
|
+
}
|
|
647
|
+
}
|
|
648
|
+
}
|
|
649
|
+
/**
|
|
650
|
+
* Wraps an inline ad as an AG-UI custom event anchored to `contentOffset`,
|
|
651
|
+
* the number of message characters emitted before the ad arrived. Clients
|
|
652
|
+
* use it to render the ad directly after that content instead of at the
|
|
653
|
+
* end of the message.
|
|
654
|
+
*/
|
|
655
|
+
makeInlineAdEvent(ad, messageId, contentOffset) {
|
|
656
|
+
const value = {
|
|
657
|
+
...ad,
|
|
658
|
+
message_id: messageId,
|
|
659
|
+
content_offset: contentOffset
|
|
660
|
+
};
|
|
661
|
+
return {
|
|
662
|
+
type: "CUSTOM_EVENT",
|
|
663
|
+
name: "adzen_inline_ad",
|
|
664
|
+
messageId,
|
|
665
|
+
value
|
|
666
|
+
};
|
|
667
|
+
}
|
|
668
|
+
makePlacementEvent(placement, adId) {
|
|
669
|
+
return {
|
|
670
|
+
type: "CUSTOM_EVENT",
|
|
671
|
+
name: "adzen_placement",
|
|
672
|
+
messageId: adId,
|
|
673
|
+
value: {
|
|
674
|
+
...placement,
|
|
675
|
+
message_id: placement.ad_id
|
|
676
|
+
}
|
|
677
|
+
};
|
|
678
|
+
}
|
|
679
|
+
mapPlacementRecord(record, messageId) {
|
|
680
|
+
return {
|
|
681
|
+
ad_id: String(record.ad_id),
|
|
682
|
+
advertiser_name: "",
|
|
683
|
+
headline: record.headline,
|
|
684
|
+
cta_text: record.cta_text,
|
|
685
|
+
destination_url: record.destination_url,
|
|
686
|
+
adUnitPosition: record.placement || this.adUnitPosition,
|
|
687
|
+
render_impression_url: record.tracking.impression_url,
|
|
688
|
+
view_impression_url: record.tracking.impression_url
|
|
689
|
+
};
|
|
690
|
+
}
|
|
691
|
+
};
|
|
259
692
|
// Annotate the CommonJS export names for ESM import in node:
|
|
260
693
|
0 && (module.exports = {
|
|
261
694
|
AdzenAsyncMiddleware,
|
|
262
|
-
|
|
695
|
+
AdzenStreamMiddleware
|
|
263
696
|
});
|
|
264
697
|
//# sourceMappingURL=index.cjs.map
|