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