@adzenai/ai 1.1.1 → 1.2.1

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.
@@ -37,7 +37,7 @@ var AdzenAsyncMiddleware = class {
37
37
  constructor(config) {
38
38
  this.config = {
39
39
  ...config,
40
- endpointUrl: config.endpointUrl ?? "https://api.adzen.ai/v1/ai",
40
+ endpointUrl: config.endpointUrl ?? "https://api.adzen.ai/v1",
41
41
  timeoutMs: config.timeoutMs ?? 3e3,
42
42
  adUnitPosition: config.adUnitPosition ?? "chin"
43
43
  };
@@ -150,15 +150,17 @@ var AdzenAsyncMiddleware = class {
150
150
  };
151
151
  if (this.config.location) body.location = this.config.location;
152
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;
153
161
  const res = await fetch(`${this.config.endpointUrl}/process`, {
154
162
  method: "POST",
155
- headers: {
156
- "Content-Type": "application/json",
157
- "X-API-Key": this.config.apiKey,
158
- // AI-API requires an Idempotency-Key on /process (400 otherwise).
159
- // One per ad fetch — each completed message is a distinct request.
160
- "Idempotency-Key": generateIdempotencyKey()
161
- },
163
+ headers,
162
164
  body: JSON.stringify(body),
163
165
  signal: controller.signal
164
166
  });
@@ -203,7 +205,475 @@ var AdzenAsyncMiddleware = class {
203
205
  };
204
206
  }
205
207
  };
208
+
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
+ }
250
+ }
251
+ if (dataLines.length > 0) {
252
+ yield { event: currentEvent, data: dataLines.join("\n") };
253
+ }
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";
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
+ customHeaders;
289
+ conversationId;
290
+ abortController = null;
291
+ placementEndpoint = null;
292
+ pollTimer = null;
293
+ streamEnded = false;
294
+ inlineAdQueue = [];
295
+ streamPromise = null;
296
+ prefetchPromise = null;
297
+ onInlineAd;
298
+ onPlacement;
299
+ onError;
300
+ onStreamStart;
301
+ onStreamEnd;
302
+ constructor(config) {
303
+ this.endpointUrl = config.endpointUrl ?? DEFAULT_ENDPOINT;
304
+ this.apiKey = config.apiKey;
305
+ this.timeoutMs = config.timeoutMs ?? DEFAULT_TIMEOUT_MS;
306
+ this.location = config.location;
307
+ this.profileId = config.profileId;
308
+ this.placementPollIntervalMs = config.placementPollIntervalMs ?? DEFAULT_POLL_INTERVAL_MS;
309
+ this.prefetchEnabled = config.prefetch ?? false;
310
+ this.sidecarEnabled = config.sidecar ?? true;
311
+ this.customHeaders = config.headers ?? {};
312
+ this.conversationId = config.conversationId ?? uuidv4();
313
+ this.onInlineAd = config.onInlineAd;
314
+ this.onPlacement = config.onPlacement;
315
+ this.onError = config.onError;
316
+ this.onStreamStart = config.onStreamStart;
317
+ this.onStreamEnd = config.onStreamEnd;
318
+ }
319
+ getConversationId() {
320
+ return this.conversationId;
321
+ }
322
+ setConversationId(id) {
323
+ this.conversationId = id;
324
+ }
325
+ /**
326
+ * Dispatches the prefetch without waiting for it.
327
+ *
328
+ * The request leaves before `connect()` opens `/stream`, which is all the
329
+ * 2-step chained flow needs — the two calls are correlated server-side by
330
+ * `conversation_id`. Awaiting the response here would stall the assistant
331
+ * message behind a full ad-matching round trip while the LLM keeps
332
+ * streaming, so the buffered tokens would then arrive in one burst.
333
+ */
334
+ startPrefetch(prompt) {
335
+ if (!this.prefetchEnabled) return;
336
+ this.prefetchPromise = this.prefetchProcess(prompt);
337
+ }
338
+ /**
339
+ * 2-step chained flow: POST /process with the user prompt and
340
+ * conversationId. Awaits the response (or timeout) but discards the
341
+ * result. If it fails, logs and continues — the stream will proceed
342
+ * in degraded mode.
343
+ */
344
+ async prefetchProcess(prompt) {
345
+ if (!this.prefetchEnabled) return;
346
+ const controller = new AbortController();
347
+ const timeout = setTimeout(() => controller.abort(), this.timeoutMs);
348
+ try {
349
+ const body = {
350
+ content: prompt,
351
+ message_id: uuidv4(),
352
+ conversation_id: this.conversationId
353
+ };
354
+ if (this.location) body.location = this.location;
355
+ const headers = {
356
+ "Content-Type": "application/json",
357
+ "X-API-Key": this.apiKey,
358
+ "Idempotency-Key": uuidv4()
359
+ };
360
+ if (this.profileId) headers["X-Profile-Id"] = this.profileId;
361
+ await fetch(`${this.endpointUrl}/process`, {
362
+ method: "POST",
363
+ headers: this.mergeHeaders(headers),
364
+ body: JSON.stringify(body),
365
+ signal: controller.signal
366
+ });
367
+ } catch {
368
+ } finally {
369
+ clearTimeout(timeout);
370
+ }
371
+ }
372
+ /**
373
+ * Opens a streaming POST to /stream, piping the upstream LLM response
374
+ * body directly. The API reads the raw LLM stream in real time and
375
+ * sends SSE ad events back on the response side.
376
+ *
377
+ * @param messageId Unique ID for this assistant message
378
+ * @param body The upstream LLM response body (one branch of a tee)
379
+ */
380
+ connect(messageId, body) {
381
+ this.streamEnded = false;
382
+ this.inlineAdQueue = [];
383
+ this.placementEndpoint = null;
384
+ this.streamPromise = null;
385
+ this.abortController = new AbortController();
386
+ if (!SUPPORTS_REQUEST_STREAMS) {
387
+ this.streamEnded = true;
388
+ this.onError?.({
389
+ type: "adzen_error",
390
+ code: "request_streams_unsupported",
391
+ 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."
392
+ });
393
+ void body.cancel().catch(() => {
394
+ });
395
+ return;
396
+ }
397
+ const headers = {
398
+ "Content-Type": "application/octet-stream",
399
+ "X-API-Key": this.apiKey,
400
+ "Idempotency-Key": uuidv4(),
401
+ "X-Message-Id": messageId,
402
+ "X-Conversation-Id": this.conversationId
403
+ };
404
+ if (this.profileId) headers["X-Profile-Id"] = this.profileId;
405
+ if (this.location) headers["X-Location"] = this.location;
406
+ this.streamPromise = this.executeStreamFetch(body, this.mergeHeaders(headers));
407
+ }
408
+ /**
409
+ * Waits for the SSE response stream from the API to finish, along with any
410
+ * prefetch still in flight. Neither can reject — both swallow their errors.
411
+ */
412
+ async finalize() {
413
+ await Promise.all([this.prefetchPromise, this.streamPromise]);
414
+ }
415
+ /**
416
+ * Merges caller-provided passthrough headers with the SDK's fixed headers.
417
+ * Fixed headers are spread last so SDK-managed keys (auth, content-type,
418
+ * correlation IDs) always win over any passthrough value.
419
+ */
420
+ mergeHeaders(fixed) {
421
+ return { ...this.customHeaders, ...fixed };
422
+ }
423
+ async executeStreamFetch(body, headers) {
424
+ const timeout = setTimeout(
425
+ () => this.abortController?.abort(),
426
+ this.timeoutMs
427
+ );
428
+ try {
429
+ const res = await fetch(`${this.endpointUrl}/stream`, {
430
+ method: "POST",
431
+ headers,
432
+ body,
433
+ signal: this.abortController?.signal,
434
+ // @ts-expect-error -- required for streaming request bodies (Node/undici)
435
+ duplex: "half"
436
+ });
437
+ clearTimeout(timeout);
438
+ if (!res.ok || !res.body) return;
439
+ await this.readStream(res.body);
440
+ } catch {
441
+ clearTimeout(timeout);
442
+ }
443
+ }
444
+ /** Drains and returns any queued inline ad events. */
445
+ drainInlineAds() {
446
+ const ads = this.inlineAdQueue.splice(0);
447
+ return ads;
448
+ }
449
+ /** Cancels pending requests and cleans up state. */
450
+ abort() {
451
+ this.abortController?.abort();
452
+ this.stopPolling();
453
+ this.streamEnded = true;
454
+ }
455
+ /** Polls the placement endpoint once. Called on an interval internally. */
456
+ async pollPlacements() {
457
+ if (!this.sidecarEnabled || !this.placementEndpoint) return;
458
+ try {
459
+ const res = await fetch(this.placementEndpoint, {
460
+ method: "GET",
461
+ headers: this.mergeHeaders({ "X-API-Key": this.apiKey })
462
+ });
463
+ if (!res.ok) return;
464
+ const data = await res.json();
465
+ for (const placement of data.placements) {
466
+ this.onPlacement?.(placement);
467
+ }
468
+ } catch {
469
+ }
470
+ }
471
+ async readStream(body) {
472
+ try {
473
+ for await (const frame of parseSSE(body)) {
474
+ if (this.abortController?.signal.aborted) break;
475
+ this.handleFrame(frame.event, frame.data);
476
+ }
477
+ } catch {
478
+ } finally {
479
+ this.streamEnded = true;
480
+ this.stopPolling();
481
+ }
482
+ }
483
+ handleFrame(event, data) {
484
+ switch (event) {
485
+ case "adzen_stream_start": {
486
+ try {
487
+ const parsed = JSON.parse(data);
488
+ const evt = { type: "adzen_stream_start", ...parsed };
489
+ this.placementEndpoint = parsed.placement_endpoint;
490
+ this.onStreamStart?.(evt);
491
+ this.startPolling();
492
+ } catch {
493
+ }
494
+ break;
495
+ }
496
+ case "adzen_inline": {
497
+ try {
498
+ const ad = JSON.parse(data);
499
+ this.inlineAdQueue.push(ad);
500
+ this.onInlineAd?.(ad);
501
+ } catch {
502
+ }
503
+ break;
504
+ }
505
+ case "adzen_error": {
506
+ try {
507
+ const parsed = JSON.parse(data);
508
+ this.onError?.({ type: "adzen_error", ...parsed });
509
+ } catch {
510
+ }
511
+ break;
512
+ }
513
+ case "adzen_stream_end": {
514
+ try {
515
+ const parsed = JSON.parse(data);
516
+ const evt = { type: "adzen_stream_end", ...parsed };
517
+ this.onStreamEnd?.(evt);
518
+ } catch {
519
+ }
520
+ this.streamEnded = true;
521
+ this.stopPolling();
522
+ break;
523
+ }
524
+ default: {
525
+ if (event?.startsWith("adzen_debug_")) break;
526
+ break;
527
+ }
528
+ }
529
+ }
530
+ startPolling() {
531
+ if (!this.sidecarEnabled) return;
532
+ if (this.pollTimer || !this.placementEndpoint) return;
533
+ this.pollTimer = setInterval(
534
+ () => this.pollPlacements(),
535
+ this.placementPollIntervalMs
536
+ );
537
+ }
538
+ stopPolling() {
539
+ if (this.pollTimer) {
540
+ clearInterval(this.pollTimer);
541
+ this.pollTimer = null;
542
+ }
543
+ }
544
+ };
545
+
546
+ // src/copilotkit/streamMiddleware.ts
547
+ var AdzenStreamMiddleware = class {
548
+ config;
549
+ adUnitPosition;
550
+ constructor(config) {
551
+ this.config = config;
552
+ this.adUnitPosition = config.adUnitPosition ?? "chin";
553
+ }
554
+ async *processStream(events, options) {
555
+ const sidecarPlacements = [];
556
+ let client = null;
557
+ let currentMessageId = "";
558
+ let contentOffset = 0;
559
+ const onPlacement = (record) => {
560
+ sidecarPlacements.push(this.mapPlacementRecord(record, currentMessageId));
561
+ };
562
+ const onError = (error) => {
563
+ console.warn(`[adzen-stream] ${error.code}: ${error.message}`);
564
+ };
565
+ for await (const event of events) {
566
+ const type = String(event.type ?? "");
567
+ switch (type) {
568
+ case "RUN_STARTED":
569
+ case "RunStarted": {
570
+ const threadId = String(event.threadId ?? "");
571
+ client = new AdzenStreamClient({
572
+ ...this.config,
573
+ conversationId: threadId || this.config.conversationId,
574
+ onPlacement,
575
+ onError
576
+ });
577
+ if (this.config.prefetch && options?.prompt) {
578
+ client.startPrefetch(options.prompt);
579
+ }
580
+ yield event;
581
+ break;
582
+ }
583
+ case "TEXT_MESSAGE_START":
584
+ case "TextMessageStart": {
585
+ currentMessageId = String(event.messageId ?? "");
586
+ contentOffset = 0;
587
+ if (client && options?.upstreamBody) {
588
+ client.connect(currentMessageId, options.upstreamBody);
589
+ }
590
+ yield event;
591
+ break;
592
+ }
593
+ case "TEXT_MESSAGE_CONTENT":
594
+ case "TextMessageContent": {
595
+ yield event;
596
+ contentOffset += String(event.delta ?? "").length;
597
+ if (client) {
598
+ for (const ad of client.drainInlineAds()) {
599
+ yield this.makeInlineAdEvent(ad, currentMessageId, contentOffset);
600
+ }
601
+ }
602
+ break;
603
+ }
604
+ case "TEXT_MESSAGE_END":
605
+ case "TextMessageEnd": {
606
+ if (client) {
607
+ await client.finalize();
608
+ for (const ad of client.drainInlineAds()) {
609
+ yield this.makeInlineAdEvent(ad, currentMessageId, contentOffset);
610
+ }
611
+ }
612
+ yield event;
613
+ break;
614
+ }
615
+ case "RUN_FINISHED":
616
+ case "RunFinished": {
617
+ if (client) {
618
+ await client.pollPlacements();
619
+ client.abort();
620
+ }
621
+ for (const placement of sidecarPlacements) {
622
+ yield this.makePlacementEvent(placement, placement.ad_id);
623
+ }
624
+ yield event;
625
+ break;
626
+ }
627
+ default:
628
+ yield event;
629
+ }
630
+ }
631
+ }
632
+ /**
633
+ * Wraps an inline ad as an AG-UI custom event anchored to `contentOffset`,
634
+ * the number of message characters emitted before the ad arrived. Clients
635
+ * use it to render the ad directly after that content instead of at the
636
+ * end of the message.
637
+ */
638
+ makeInlineAdEvent(ad, messageId, contentOffset) {
639
+ const value = {
640
+ ...ad,
641
+ message_id: messageId,
642
+ content_offset: contentOffset
643
+ };
644
+ return {
645
+ type: "CUSTOM_EVENT",
646
+ name: "adzen_inline_ad",
647
+ messageId,
648
+ value
649
+ };
650
+ }
651
+ makePlacementEvent(placement, adId) {
652
+ return {
653
+ type: "CUSTOM_EVENT",
654
+ name: "adzen_placement",
655
+ messageId: adId,
656
+ value: {
657
+ ...placement,
658
+ message_id: placement.ad_id
659
+ }
660
+ };
661
+ }
662
+ mapPlacementRecord(record, messageId) {
663
+ return {
664
+ ad_id: String(record.ad_id),
665
+ advertiser_name: "",
666
+ headline: record.headline,
667
+ cta_text: record.cta_text,
668
+ destination_url: record.destination_url,
669
+ adUnitPosition: record.placement || this.adUnitPosition,
670
+ render_impression_url: record.tracking.impression_url,
671
+ view_impression_url: record.tracking.impression_url
672
+ };
673
+ }
674
+ };
206
675
  export {
207
- AdzenAsyncMiddleware
676
+ AdzenAsyncMiddleware,
677
+ AdzenStreamMiddleware
208
678
  };
209
679
  //# sourceMappingURL=index.js.map
@@ -1 +1 @@
1
- {"version":3,"sources":["../../src/copilotkit/middleware.ts"],"sourcesContent":["import type { AdzenPlacement, ProcessResponse, ProcessResponseAd } from \"@adzenai/core\";\n\nexport interface AdzenAsyncConfig {\n endpointUrl?: string;\n apiKey: string;\n timeoutMs?: number;\n adUnitPosition?: string;\n location?: string;\n conversationId?: string;\n}\n\ninterface MessageBuffer {\n content: string;\n startedAt: number;\n fetched: boolean;\n}\n\nfunction generateIdempotencyKey(): string {\n const globalCrypto = (globalThis as { crypto?: { randomUUID?: () => string } })\n .crypto;\n if (globalCrypto?.randomUUID) return globalCrypto.randomUUID();\n return `adzen-${Date.now()}-${Math.random().toString(36).slice(2)}`;\n}\n\nfunction isFinalOutputEvent(event: Record<string, unknown>): boolean {\n const type = String(event.type ?? \"\");\n if (type === \"RUN_FINISHED\" || type === \"RunFinished\") return false;\n if (type !== \"CUSTOM\" && type !== \"CUSTOM_EVENT\" && type !== \"CustomEvent\")\n return false;\n if (event.final === true || event.isFinal === true) return true;\n const name = String(event.name ?? event.event ?? \"\").toLowerCase();\n return name.includes(\"assistant_final\") || name.includes(\"final_output\");\n}\n\nfunction extractContentFromEvent(event: Record<string, unknown>): string | null {\n for (const key of [\"output\", \"result\", \"data\", \"payload\", \"message\"]) {\n const val = event[key];\n if (typeof val === \"string\" && val.length > 0) return val;\n if (typeof val === \"object\" && val !== null) {\n try {\n return JSON.stringify(val);\n } catch {\n continue;\n }\n }\n }\n return null;\n}\n\n/**\n * AG-UI middleware that intercepts the event stream and calls AI-API after\n * each assistant message, emitting `adzen_placement` custom events when an\n * ad is returned.\n *\n * All upstream events pass through immediately — zero latency is added to\n * the text stream. Ad fetches run as a side-effect and `RUN_FINISHED` is\n * held until all pending fetches settle.\n */\nexport class AdzenAsyncMiddleware {\n private config: Required<Pick<AdzenAsyncConfig, \"endpointUrl\" | \"timeoutMs\" | \"adUnitPosition\">> &\n AdzenAsyncConfig;\n private buffers = new Map<string, MessageBuffer>();\n private fetchedIds = new Set<string>();\n private pendingFetches: Promise<void>[] = [];\n private fallbackCounter = 0;\n private conversationId: string | undefined;\n\n constructor(config: AdzenAsyncConfig) {\n this.config = {\n ...config,\n endpointUrl: config.endpointUrl ?? \"https://api.adzen.ai/v1/ai\",\n timeoutMs: config.timeoutMs ?? 3000,\n adUnitPosition: config.adUnitPosition ?? \"chin\",\n };\n this.conversationId = config.conversationId;\n }\n\n /**\n * Process a single AG-UI event. Returns an array of events to emit\n * downstream (always includes the original event, possibly followed\n * by an `adzen_placement` custom event).\n */\n async processEvent(event: Record<string, unknown>): Promise<Record<string, unknown>[]> {\n const type = String(event.type ?? \"\");\n\n switch (type) {\n case \"RUN_STARTED\":\n case \"RunStarted\": {\n const threadId = String(event.threadId ?? \"\");\n if (threadId) this.conversationId = threadId;\n return [event];\n }\n\n case \"TEXT_MESSAGE_START\":\n case \"TextMessageStart\": {\n const messageId = this.resolveMessageId(event);\n this.evictStaleBuffers();\n this.buffers.set(messageId, {\n content: \"\",\n startedAt: Date.now(),\n fetched: false,\n });\n return [event];\n }\n\n case \"TEXT_MESSAGE_CONTENT\":\n case \"TextMessageContent\": {\n const messageId = String(event.messageId ?? \"\");\n const buf = this.buffers.get(messageId);\n if (buf) buf.content += String(event.delta ?? \"\");\n return [event];\n }\n\n case \"TEXT_MESSAGE_END\":\n case \"TextMessageEnd\": {\n const messageId = String(event.messageId ?? \"\");\n const buf = this.buffers.get(messageId);\n const out: Record<string, unknown>[] = [event];\n\n if (buf && !buf.fetched) {\n buf.fetched = true;\n this.fetchedIds.add(messageId);\n const fetchPromise = this.fetchAd(buf.content, messageId).then(\n (placement) => {\n if (placement) out.push(this.makePlacementEvent(placement, messageId));\n },\n );\n this.pendingFetches.push(fetchPromise);\n await fetchPromise;\n this.buffers.delete(messageId);\n }\n\n return out;\n }\n\n case \"CUSTOM\":\n case \"CUSTOM_EVENT\":\n case \"CustomEvent\": {\n if (isFinalOutputEvent(event)) {\n const messageId = String(event.messageId ?? \"\");\n if (!this.fetchedIds.has(messageId)) {\n const buf = this.buffers.get(messageId);\n const content = extractContentFromEvent(event);\n if (content) {\n const out: Record<string, unknown>[] = [event];\n if (buf) buf.fetched = true;\n this.fetchedIds.add(messageId);\n const fetchPromise = this.fetchAd(content, messageId).then(\n (placement) => {\n if (placement) out.push(this.makePlacementEvent(placement, messageId));\n },\n );\n this.pendingFetches.push(fetchPromise);\n await fetchPromise;\n return out;\n }\n }\n }\n return [event];\n }\n\n case \"RUN_FINISHED\":\n case \"RunFinished\": {\n await Promise.allSettled(this.pendingFetches);\n this.pendingFetches = [];\n return [event];\n }\n\n default:\n return [event];\n }\n }\n\n private resolveMessageId(event: Record<string, unknown>): string {\n const id = String(event.messageId ?? \"\");\n if (id) return id;\n return `__adzen_fallback_${Date.now()}_${this.fallbackCounter++}`;\n }\n\n private evictStaleBuffers(): void {\n const cutoff = Date.now() - this.config.timeoutMs * 2;\n for (const [key, buf] of this.buffers) {\n if (buf.startedAt < cutoff) this.buffers.delete(key);\n }\n }\n\n private async fetchAd(\n content: string,\n messageId: string,\n ): Promise<AdzenPlacement | null> {\n const controller = new AbortController();\n const timeout = setTimeout(() => controller.abort(), this.config.timeoutMs);\n\n try {\n const body: Record<string, unknown> = {\n content,\n message_id: messageId,\n };\n if (this.config.location) body.location = this.config.location;\n if (this.conversationId) body.conversation_id = this.conversationId;\n\n const res = await fetch(`${this.config.endpointUrl}/process`, {\n method: \"POST\",\n headers: {\n \"Content-Type\": \"application/json\",\n \"X-API-Key\": this.config.apiKey,\n // AI-API requires an Idempotency-Key on /process (400 otherwise).\n // One per ad fetch — each completed message is a distinct request.\n \"Idempotency-Key\": generateIdempotencyKey(),\n },\n body: JSON.stringify(body),\n signal: controller.signal,\n });\n\n if (!res.ok) return null;\n\n const data = (await res.json()) as ProcessResponse;\n const first = data.ads?.[0];\n if (!first) return null;\n return this.mapResponseToPlacement(first);\n } catch {\n return null;\n } finally {\n clearTimeout(timeout);\n }\n }\n\n private mapResponseToPlacement(ad: ProcessResponseAd): AdzenPlacement {\n return {\n ad_id: String(ad.ad_id),\n advertiser_name: ad.advertiser_name,\n advertiser_image_url: ad.sponsor_logo_url || undefined,\n headline: ad.title,\n description: ad.description,\n cta_text: ad.cta,\n destination_url: ad.click_through_url,\n creative_url: ad.creative_url ?? undefined,\n adUnitPosition: ad.placement ?? this.config.adUnitPosition,\n render_impression_url: ad.render_impression_url,\n view_impression_url: ad.view_impression_url,\n };\n }\n\n private makePlacementEvent(\n placement: AdzenPlacement,\n messageId: string,\n ): Record<string, unknown> {\n return {\n // AG-UI's event schema only accepts \"CUSTOM\"; @ag-ui/client zod-validates\n // every SSE frame and aborts the run on an unknown type. \"CUSTOM_EVENT\"\n // was rejected downstream.\n type: \"CUSTOM\",\n name: \"adzen_placement\",\n messageId,\n value: {\n ...placement,\n message_id: messageId,\n },\n };\n }\n}\n"],"mappings":"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","names":[]}
1
+ {"version":3,"sources":["../../src/copilotkit/middleware.ts","../../src/copilotkit/AdzenStreamClient.ts","../../src/copilotkit/parseSSE.ts","../../src/copilotkit/streamMiddleware.ts"],"sourcesContent":["import type { AdzenPlacement, ProcessResponse, ProcessResponseAd } from \"@adzenai/core\";\n\nexport interface AdzenAsyncConfig {\n endpointUrl?: string;\n apiKey: string;\n timeoutMs?: number;\n adUnitPosition?: string;\n location?: string;\n conversationId?: string;\n profileId?: string;\n}\n\ninterface MessageBuffer {\n content: string;\n startedAt: number;\n fetched: boolean;\n}\n\nfunction generateIdempotencyKey(): string {\n const globalCrypto = (globalThis as { crypto?: { randomUUID?: () => string } })\n .crypto;\n if (globalCrypto?.randomUUID) return globalCrypto.randomUUID();\n return `adzen-${Date.now()}-${Math.random().toString(36).slice(2)}`;\n}\n\nfunction isFinalOutputEvent(event: Record<string, unknown>): boolean {\n const type = String(event.type ?? \"\");\n if (type === \"RUN_FINISHED\" || type === \"RunFinished\") return false;\n if (type !== \"CUSTOM\" && type !== \"CUSTOM_EVENT\" && type !== \"CustomEvent\")\n return false;\n if (event.final === true || event.isFinal === true) return true;\n const name = String(event.name ?? event.event ?? \"\").toLowerCase();\n return name.includes(\"assistant_final\") || name.includes(\"final_output\");\n}\n\nfunction extractContentFromEvent(event: Record<string, unknown>): string | null {\n for (const key of [\"output\", \"result\", \"data\", \"payload\", \"message\"]) {\n const val = event[key];\n if (typeof val === \"string\" && val.length > 0) return val;\n if (typeof val === \"object\" && val !== null) {\n try {\n return JSON.stringify(val);\n } catch {\n continue;\n }\n }\n }\n return null;\n}\n\n/**\n * AG-UI middleware that intercepts the event stream and calls AI-API after\n * each assistant message, emitting `adzen_placement` custom events when an\n * ad is returned.\n *\n * All upstream events pass through immediately — zero latency is added to\n * the text stream. Ad fetches run as a side-effect and `RUN_FINISHED` is\n * held until all pending fetches settle.\n */\nexport class AdzenAsyncMiddleware {\n private config: Required<Pick<AdzenAsyncConfig, \"endpointUrl\" | \"timeoutMs\" | \"adUnitPosition\">> &\n AdzenAsyncConfig;\n private buffers = new Map<string, MessageBuffer>();\n private fetchedIds = new Set<string>();\n private pendingFetches: Promise<void>[] = [];\n private fallbackCounter = 0;\n private conversationId: string | undefined;\n\n constructor(config: AdzenAsyncConfig) {\n this.config = {\n ...config,\n endpointUrl: config.endpointUrl ?? \"https://api.adzen.ai/v1\",\n timeoutMs: config.timeoutMs ?? 3000,\n adUnitPosition: config.adUnitPosition ?? \"chin\",\n };\n this.conversationId = config.conversationId;\n }\n\n /**\n * Process a single AG-UI event. Returns an array of events to emit\n * downstream (always includes the original event, possibly followed\n * by an `adzen_placement` custom event).\n */\n async processEvent(event: Record<string, unknown>): Promise<Record<string, unknown>[]> {\n const type = String(event.type ?? \"\");\n\n switch (type) {\n case \"RUN_STARTED\":\n case \"RunStarted\": {\n const threadId = String(event.threadId ?? \"\");\n if (threadId) this.conversationId = threadId;\n return [event];\n }\n\n case \"TEXT_MESSAGE_START\":\n case \"TextMessageStart\": {\n const messageId = this.resolveMessageId(event);\n this.evictStaleBuffers();\n this.buffers.set(messageId, {\n content: \"\",\n startedAt: Date.now(),\n fetched: false,\n });\n return [event];\n }\n\n case \"TEXT_MESSAGE_CONTENT\":\n case \"TextMessageContent\": {\n const messageId = String(event.messageId ?? \"\");\n const buf = this.buffers.get(messageId);\n if (buf) buf.content += String(event.delta ?? \"\");\n return [event];\n }\n\n case \"TEXT_MESSAGE_END\":\n case \"TextMessageEnd\": {\n const messageId = String(event.messageId ?? \"\");\n const buf = this.buffers.get(messageId);\n const out: Record<string, unknown>[] = [event];\n\n if (buf && !buf.fetched) {\n buf.fetched = true;\n this.fetchedIds.add(messageId);\n const fetchPromise = this.fetchAd(buf.content, messageId).then(\n (placement) => {\n if (placement) out.push(this.makePlacementEvent(placement, messageId));\n },\n );\n this.pendingFetches.push(fetchPromise);\n await fetchPromise;\n this.buffers.delete(messageId);\n }\n\n return out;\n }\n\n case \"CUSTOM\":\n case \"CUSTOM_EVENT\":\n case \"CustomEvent\": {\n if (isFinalOutputEvent(event)) {\n const messageId = String(event.messageId ?? \"\");\n if (!this.fetchedIds.has(messageId)) {\n const buf = this.buffers.get(messageId);\n const content = extractContentFromEvent(event);\n if (content) {\n const out: Record<string, unknown>[] = [event];\n if (buf) buf.fetched = true;\n this.fetchedIds.add(messageId);\n const fetchPromise = this.fetchAd(content, messageId).then(\n (placement) => {\n if (placement) out.push(this.makePlacementEvent(placement, messageId));\n },\n );\n this.pendingFetches.push(fetchPromise);\n await fetchPromise;\n return out;\n }\n }\n }\n return [event];\n }\n\n case \"RUN_FINISHED\":\n case \"RunFinished\": {\n await Promise.allSettled(this.pendingFetches);\n this.pendingFetches = [];\n return [event];\n }\n\n default:\n return [event];\n }\n }\n\n private resolveMessageId(event: Record<string, unknown>): string {\n const id = String(event.messageId ?? \"\");\n if (id) return id;\n return `__adzen_fallback_${Date.now()}_${this.fallbackCounter++}`;\n }\n\n private evictStaleBuffers(): void {\n const cutoff = Date.now() - this.config.timeoutMs * 2;\n for (const [key, buf] of this.buffers) {\n if (buf.startedAt < cutoff) this.buffers.delete(key);\n }\n }\n\n private async fetchAd(\n content: string,\n messageId: string,\n ): Promise<AdzenPlacement | null> {\n const controller = new AbortController();\n const timeout = setTimeout(() => controller.abort(), this.config.timeoutMs);\n\n try {\n const body: Record<string, unknown> = {\n content,\n message_id: messageId,\n };\n if (this.config.location) body.location = this.config.location;\n if (this.conversationId) body.conversation_id = this.conversationId;\n\n const headers: Record<string, string> = {\n \"Content-Type\": \"application/json\",\n \"X-API-Key\": this.config.apiKey,\n // AI-API requires an Idempotency-Key on /process (400 otherwise).\n // One per ad fetch — each completed message is a distinct request.\n \"Idempotency-Key\": generateIdempotencyKey(),\n };\n if (this.config.profileId) headers[\"X-Profile-Id\"] = this.config.profileId;\n\n const res = await fetch(`${this.config.endpointUrl}/process`, {\n method: \"POST\",\n headers,\n body: JSON.stringify(body),\n signal: controller.signal,\n });\n\n if (!res.ok) return null;\n\n const data = (await res.json()) as ProcessResponse;\n const first = data.ads?.[0];\n if (!first) return null;\n return this.mapResponseToPlacement(first);\n } catch {\n return null;\n } finally {\n clearTimeout(timeout);\n }\n }\n\n private mapResponseToPlacement(ad: ProcessResponseAd): AdzenPlacement {\n return {\n ad_id: String(ad.ad_id),\n advertiser_name: ad.advertiser_name,\n advertiser_image_url: ad.sponsor_logo_url || undefined,\n headline: ad.title,\n description: ad.description,\n cta_text: ad.cta,\n destination_url: ad.click_through_url,\n creative_url: ad.creative_url ?? undefined,\n adUnitPosition: ad.placement ?? this.config.adUnitPosition,\n render_impression_url: ad.render_impression_url,\n view_impression_url: ad.view_impression_url,\n };\n }\n\n private makePlacementEvent(\n placement: AdzenPlacement,\n messageId: string,\n ): Record<string, unknown> {\n return {\n // AG-UI's event schema only accepts \"CUSTOM\"; @ag-ui/client zod-validates\n // every SSE frame and aborts the run on an unknown type. \"CUSTOM_EVENT\"\n // was rejected downstream.\n type: \"CUSTOM\",\n name: \"adzen_placement\",\n messageId,\n value: {\n ...placement,\n message_id: messageId,\n },\n };\n }\n}\n","import type {\n InlineAdEvent,\n PlacementRecord,\n PlacementsResponse,\n AdzenStreamStartEvent,\n AdzenStreamEndEvent,\n AdzenStreamErrorEvent,\n} from \"@adzenai/core\";\nimport { uuidv4 } from \"@adzenai/core\";\nimport { parseSSE } from \"./parseSSE.js\";\n\nexport interface AdzenStreamConfig {\n apiKey: string;\n endpointUrl?: string;\n timeoutMs?: number;\n location?: string;\n conversationId?: string;\n profileId?: string;\n placementPollIntervalMs?: number;\n prefetch?: boolean;\n /**\n * Whether to poll the sidecar placement endpoint for out-of-stream ads\n * rendered outside the message body. Defaults to `true`. Set to `false`\n * to run inline-only, leaving `adzen_inline` events unaffected.\n */\n sidecar?: boolean;\n /**\n * Extra request headers merged into every outbound Adzen request\n * (/stream, /process, placement polling). Intended for internal\n * debugging (e.g. `X-Adzen-Debug-*`). SDK-managed headers\n * (auth, content-type, correlation IDs) always take precedence.\n */\n headers?: Record<string, string>;\n onInlineAd?: (ad: InlineAdEvent) => void;\n onPlacement?: (placement: PlacementRecord) => void;\n onError?: (error: AdzenStreamErrorEvent) => void;\n onStreamStart?: (event: AdzenStreamStartEvent) => void;\n onStreamEnd?: (event: AdzenStreamEndEvent) => void;\n}\n\nconst DEFAULT_ENDPOINT = \"https://api.adzen.ai/v1\";\nconst DEFAULT_TIMEOUT_MS = 3000;\nconst DEFAULT_POLL_INTERVAL_MS = 2000;\n\n/**\n * Whether the current runtime can send a ReadableStream as a fetch request\n * body. Node 18+ (undici) supports this; Safari and Firefox do not, and\n * silently coerce the stream to the string \"[object ReadableStream]\" via the\n * WebIDL BodyInit union's USVString member.\n *\n * Detection works by checking whether the `duplex` getter is read and whether\n * the runtime set a Content-Type — a coerced string body gets `text/plain`.\n */\nconst SUPPORTS_REQUEST_STREAMS = (() => {\n try {\n let duplexAccessed = false;\n const hasContentType = new Request(\"http://localhost\", {\n method: \"POST\",\n body: new ReadableStream(),\n get duplex() {\n duplexAccessed = true;\n return \"half\";\n },\n } as RequestInit).headers.has(\"Content-Type\");\n return duplexAccessed && !hasContentType;\n } catch {\n return false;\n }\n})();\n\n/**\n * Framework-agnostic client for the Adzen streaming ad enrichment API.\n *\n * Handles connecting to `/v1/stream`, parsing SSE events, optional prefetch\n * via `/process`, and sidecar placement polling. Framework adapters (AG-UI\n * middleware, React hooks) wrap this class.\n */\nexport class AdzenStreamClient {\n private endpointUrl: string;\n private apiKey: string;\n private timeoutMs: number;\n private location?: string;\n private profileId?: string;\n private placementPollIntervalMs: number;\n private prefetchEnabled: boolean;\n private sidecarEnabled: boolean;\n private customHeaders: Record<string, string>;\n\n private conversationId: string;\n private abortController: AbortController | null = null;\n private placementEndpoint: string | null = null;\n private pollTimer: ReturnType<typeof setInterval> | null = null;\n private streamEnded = false;\n private inlineAdQueue: InlineAdEvent[] = [];\n private streamPromise: Promise<void> | null = null;\n private prefetchPromise: Promise<void> | null = null;\n\n private onInlineAd?: (ad: InlineAdEvent) => void;\n private onPlacement?: (placement: PlacementRecord) => void;\n private onError?: (error: AdzenStreamErrorEvent) => void;\n private onStreamStart?: (event: AdzenStreamStartEvent) => void;\n private onStreamEnd?: (event: AdzenStreamEndEvent) => void;\n\n constructor(config: AdzenStreamConfig) {\n this.endpointUrl = config.endpointUrl ?? DEFAULT_ENDPOINT;\n this.apiKey = config.apiKey;\n this.timeoutMs = config.timeoutMs ?? DEFAULT_TIMEOUT_MS;\n this.location = config.location;\n this.profileId = config.profileId;\n this.placementPollIntervalMs =\n config.placementPollIntervalMs ?? DEFAULT_POLL_INTERVAL_MS;\n this.prefetchEnabled = config.prefetch ?? false;\n this.sidecarEnabled = config.sidecar ?? true;\n this.customHeaders = config.headers ?? {};\n this.conversationId = config.conversationId ?? uuidv4();\n\n this.onInlineAd = config.onInlineAd;\n this.onPlacement = config.onPlacement;\n this.onError = config.onError;\n this.onStreamStart = config.onStreamStart;\n this.onStreamEnd = config.onStreamEnd;\n }\n\n getConversationId(): string {\n return this.conversationId;\n }\n\n setConversationId(id: string): void {\n this.conversationId = id;\n }\n\n /**\n * Dispatches the prefetch without waiting for it.\n *\n * The request leaves before `connect()` opens `/stream`, which is all the\n * 2-step chained flow needs — the two calls are correlated server-side by\n * `conversation_id`. Awaiting the response here would stall the assistant\n * message behind a full ad-matching round trip while the LLM keeps\n * streaming, so the buffered tokens would then arrive in one burst.\n */\n startPrefetch(prompt: string): void {\n if (!this.prefetchEnabled) return;\n this.prefetchPromise = this.prefetchProcess(prompt);\n }\n\n /**\n * 2-step chained flow: POST /process with the user prompt and\n * conversationId. Awaits the response (or timeout) but discards the\n * result. If it fails, logs and continues — the stream will proceed\n * in degraded mode.\n */\n async prefetchProcess(prompt: string): Promise<void> {\n if (!this.prefetchEnabled) return;\n\n const controller = new AbortController();\n const timeout = setTimeout(() => controller.abort(), this.timeoutMs);\n\n try {\n const body: Record<string, unknown> = {\n content: prompt,\n message_id: uuidv4(),\n conversation_id: this.conversationId,\n };\n if (this.location) body.location = this.location;\n\n const headers: Record<string, string> = {\n \"Content-Type\": \"application/json\",\n \"X-API-Key\": this.apiKey,\n \"Idempotency-Key\": uuidv4(),\n };\n if (this.profileId) headers[\"X-Profile-Id\"] = this.profileId;\n\n await fetch(`${this.endpointUrl}/process`, {\n method: \"POST\",\n headers: this.mergeHeaders(headers),\n body: JSON.stringify(body),\n signal: controller.signal,\n });\n } catch {\n // Prefetch failure is non-fatal — stream continues without pre-match context\n } finally {\n clearTimeout(timeout);\n }\n }\n\n /**\n * Opens a streaming POST to /stream, piping the upstream LLM response\n * body directly. The API reads the raw LLM stream in real time and\n * sends SSE ad events back on the response side.\n *\n * @param messageId Unique ID for this assistant message\n * @param body The upstream LLM response body (one branch of a tee)\n */\n connect(messageId: string, body: ReadableStream<Uint8Array>): void {\n this.streamEnded = false;\n this.inlineAdQueue = [];\n this.placementEndpoint = null;\n this.streamPromise = null;\n this.abortController = new AbortController();\n\n if (!SUPPORTS_REQUEST_STREAMS) {\n this.streamEnded = true;\n this.onError?.({\n type: \"adzen_error\",\n code: \"request_streams_unsupported\",\n message:\n \"This runtime cannot send a ReadableStream as a request body, so the \" +\n \"LLM stream cannot be proxied. Run AdzenStreamMiddleware server-side \" +\n \"(Node 18+) — browsers other than Chromium do not support fetch upload streams.\",\n });\n void body.cancel().catch(() => {});\n return;\n }\n\n const headers: Record<string, string> = {\n \"Content-Type\": \"application/octet-stream\",\n \"X-API-Key\": this.apiKey,\n \"Idempotency-Key\": uuidv4(),\n \"X-Message-Id\": messageId,\n \"X-Conversation-Id\": this.conversationId,\n };\n if (this.profileId) headers[\"X-Profile-Id\"] = this.profileId;\n if (this.location) headers[\"X-Location\"] = this.location;\n\n this.streamPromise = this.executeStreamFetch(body, this.mergeHeaders(headers));\n }\n\n /**\n * Waits for the SSE response stream from the API to finish, along with any\n * prefetch still in flight. Neither can reject — both swallow their errors.\n */\n async finalize(): Promise<void> {\n await Promise.all([this.prefetchPromise, this.streamPromise]);\n }\n\n /**\n * Merges caller-provided passthrough headers with the SDK's fixed headers.\n * Fixed headers are spread last so SDK-managed keys (auth, content-type,\n * correlation IDs) always win over any passthrough value.\n */\n private mergeHeaders(\n fixed: Record<string, string>,\n ): Record<string, string> {\n return { ...this.customHeaders, ...fixed };\n }\n\n private async executeStreamFetch(\n body: ReadableStream<Uint8Array>,\n headers: Record<string, string>,\n ): Promise<void> {\n const timeout = setTimeout(\n () => this.abortController?.abort(),\n this.timeoutMs,\n );\n\n try {\n const res = await fetch(`${this.endpointUrl}/stream`, {\n method: \"POST\",\n headers,\n body,\n signal: this.abortController?.signal,\n // @ts-expect-error -- required for streaming request bodies (Node/undici)\n duplex: \"half\",\n });\n\n clearTimeout(timeout);\n\n if (!res.ok || !res.body) return;\n\n await this.readStream(res.body);\n } catch {\n clearTimeout(timeout);\n }\n }\n\n /** Drains and returns any queued inline ad events. */\n drainInlineAds(): InlineAdEvent[] {\n const ads = this.inlineAdQueue.splice(0);\n return ads;\n }\n\n /** Cancels pending requests and cleans up state. */\n abort(): void {\n this.abortController?.abort();\n this.stopPolling();\n this.streamEnded = true;\n }\n\n /** Polls the placement endpoint once. Called on an interval internally. */\n async pollPlacements(): Promise<void> {\n if (!this.sidecarEnabled || !this.placementEndpoint) return;\n\n try {\n const res = await fetch(this.placementEndpoint, {\n method: \"GET\",\n headers: this.mergeHeaders({ \"X-API-Key\": this.apiKey }),\n });\n if (!res.ok) return;\n\n const data = (await res.json()) as PlacementsResponse;\n for (const placement of data.placements) {\n this.onPlacement?.(placement);\n }\n } catch {\n // Polling failure is non-fatal\n }\n }\n\n private async readStream(body: ReadableStream<Uint8Array>): Promise<void> {\n try {\n for await (const frame of parseSSE(body)) {\n if (this.abortController?.signal.aborted) break;\n this.handleFrame(frame.event, frame.data);\n }\n } catch {\n // Stream read error — degrade gracefully\n } finally {\n this.streamEnded = true;\n this.stopPolling();\n }\n }\n\n private handleFrame(event: string | undefined, data: string): void {\n switch (event) {\n case \"adzen_stream_start\": {\n try {\n const parsed = JSON.parse(data) as Omit<AdzenStreamStartEvent, \"type\">;\n const evt: AdzenStreamStartEvent = { type: \"adzen_stream_start\", ...parsed };\n this.placementEndpoint = parsed.placement_endpoint;\n this.onStreamStart?.(evt);\n this.startPolling();\n } catch { /* malformed — skip */ }\n break;\n }\n case \"adzen_inline\": {\n try {\n const ad = JSON.parse(data) as InlineAdEvent;\n this.inlineAdQueue.push(ad);\n this.onInlineAd?.(ad);\n } catch { /* malformed — skip */ }\n break;\n }\n case \"adzen_error\": {\n try {\n const parsed = JSON.parse(data) as Omit<AdzenStreamErrorEvent, \"type\">;\n this.onError?.({ type: \"adzen_error\", ...parsed });\n } catch { /* malformed — skip */ }\n break;\n }\n case \"adzen_stream_end\": {\n try {\n const parsed = JSON.parse(data) as Omit<AdzenStreamEndEvent, \"type\">;\n const evt: AdzenStreamEndEvent = { type: \"adzen_stream_end\", ...parsed };\n this.onStreamEnd?.(evt);\n } catch { /* malformed — skip */ }\n this.streamEnded = true;\n this.stopPolling();\n break;\n }\n default: {\n if (event?.startsWith(\"adzen_debug_\")) break;\n break;\n }\n }\n }\n\n private startPolling(): void {\n if (!this.sidecarEnabled) return;\n if (this.pollTimer || !this.placementEndpoint) return;\n this.pollTimer = setInterval(\n () => this.pollPlacements(),\n this.placementPollIntervalMs,\n );\n }\n\n private stopPolling(): void {\n if (this.pollTimer) {\n clearInterval(this.pollTimer);\n this.pollTimer = null;\n }\n }\n}\n","/**\n * Lightweight SSE parser that reads a ReadableStream<Uint8Array> and yields\n * parsed frames. Handles multi-line `data:` fields, named event types, and\n * ignores `id:` / `retry:` fields per the SSE spec.\n */\n\nexport interface SSEFrame {\n event?: string;\n data: string;\n}\n\nexport async function* parseSSE(\n stream: ReadableStream<Uint8Array>,\n): AsyncGenerator<SSEFrame> {\n const reader = stream.getReader();\n const decoder = new TextDecoder();\n let buffer = \"\";\n let currentEvent: string | undefined;\n let dataLines: string[] = [];\n\n try {\n while (true) {\n const { done, value } = await reader.read();\n if (done) break;\n\n buffer += decoder.decode(value, { stream: true });\n const lines = buffer.split(\"\\n\");\n buffer = lines.pop()!;\n\n for (const raw of lines) {\n const line = raw.endsWith(\"\\r\") ? raw.slice(0, -1) : raw;\n\n if (line === \"\") {\n if (dataLines.length > 0) {\n yield { event: currentEvent, data: dataLines.join(\"\\n\") };\n dataLines = [];\n currentEvent = undefined;\n }\n continue;\n }\n\n if (line.startsWith(\":\")) continue;\n\n const colonIdx = line.indexOf(\":\");\n if (colonIdx === -1) continue;\n\n const field = line.slice(0, colonIdx);\n const value = line.slice(colonIdx + 1).replace(/^ /, \"\");\n\n switch (field) {\n case \"data\":\n dataLines.push(value);\n break;\n case \"event\":\n currentEvent = value;\n break;\n // id and retry are intentionally ignored\n }\n }\n }\n\n if (dataLines.length > 0) {\n yield { event: currentEvent, data: dataLines.join(\"\\n\") };\n }\n } finally {\n reader.releaseLock();\n }\n}\n","import type {\n AdzenPlacement,\n InlineAdEvent,\n InlineAdEventPayload,\n PlacementRecord,\n AdzenStreamErrorEvent,\n} from \"@adzenai/core\";\nimport { AdzenStreamClient } from \"./AdzenStreamClient.js\";\nimport type { AdzenStreamConfig } from \"./AdzenStreamClient.js\";\n\nexport interface AdzenStreamMiddlewareConfig extends AdzenStreamConfig {\n adUnitPosition?: string;\n}\n\n/**\n * AG-UI middleware that proxies the LLM response stream through Adzen's\n * streaming endpoint for real-time ad enrichment.\n *\n * Uses a stream transformer pattern: `processStream()` takes an\n * AsyncIterable of AG-UI events and yields an enriched stream that\n * includes inline ad events and sidecar placements.\n *\n * Supports the 2-step chained flow: when `prefetch: true` and a `prompt`\n * is provided, fires POST /process before opening the stream connection.\n */\nexport class AdzenStreamMiddleware {\n private config: AdzenStreamMiddlewareConfig;\n private adUnitPosition: string;\n\n constructor(config: AdzenStreamMiddlewareConfig) {\n this.config = config;\n this.adUnitPosition = config.adUnitPosition ?? \"chin\";\n }\n\n async *processStream(\n events: AsyncIterable<Record<string, unknown>>,\n options?: { prompt?: string; upstreamBody?: ReadableStream<Uint8Array> },\n ): AsyncGenerator<Record<string, unknown>> {\n const sidecarPlacements: AdzenPlacement[] = [];\n let client: AdzenStreamClient | null = null;\n let currentMessageId = \"\";\n let contentOffset = 0;\n\n const onPlacement = (record: PlacementRecord) => {\n sidecarPlacements.push(this.mapPlacementRecord(record, currentMessageId));\n };\n\n const onError = (error: AdzenStreamErrorEvent) => {\n console.warn(`[adzen-stream] ${error.code}: ${error.message}`);\n };\n\n for await (const event of events) {\n const type = String(event.type ?? \"\");\n\n switch (type) {\n case \"RUN_STARTED\":\n case \"RunStarted\": {\n const threadId = String(event.threadId ?? \"\");\n\n client = new AdzenStreamClient({\n ...this.config,\n conversationId: threadId || this.config.conversationId,\n onPlacement,\n onError,\n });\n\n if (this.config.prefetch && options?.prompt) {\n client.startPrefetch(options.prompt);\n }\n\n yield event;\n break;\n }\n\n case \"TEXT_MESSAGE_START\":\n case \"TextMessageStart\": {\n currentMessageId = String(event.messageId ?? \"\");\n contentOffset = 0;\n if (client && options?.upstreamBody) {\n client.connect(currentMessageId, options.upstreamBody);\n }\n yield event;\n break;\n }\n\n case \"TEXT_MESSAGE_CONTENT\":\n case \"TextMessageContent\": {\n yield event;\n contentOffset += String(event.delta ?? \"\").length;\n\n if (client) {\n for (const ad of client.drainInlineAds()) {\n yield this.makeInlineAdEvent(ad, currentMessageId, contentOffset);\n }\n }\n break;\n }\n\n case \"TEXT_MESSAGE_END\":\n case \"TextMessageEnd\": {\n if (client) {\n await client.finalize();\n for (const ad of client.drainInlineAds()) {\n yield this.makeInlineAdEvent(ad, currentMessageId, contentOffset);\n }\n }\n yield event;\n break;\n }\n\n case \"RUN_FINISHED\":\n case \"RunFinished\": {\n if (client) {\n await client.pollPlacements();\n client.abort();\n }\n\n for (const placement of sidecarPlacements) {\n yield this.makePlacementEvent(placement, placement.ad_id);\n }\n\n yield event;\n break;\n }\n\n default:\n yield event;\n }\n }\n }\n\n /**\n * Wraps an inline ad as an AG-UI custom event anchored to `contentOffset`,\n * the number of message characters emitted before the ad arrived. Clients\n * use it to render the ad directly after that content instead of at the\n * end of the message.\n */\n private makeInlineAdEvent(\n ad: InlineAdEvent,\n messageId: string,\n contentOffset: number,\n ): Record<string, unknown> {\n const value: InlineAdEventPayload = {\n ...ad,\n message_id: messageId,\n content_offset: contentOffset,\n };\n return {\n type: \"CUSTOM_EVENT\",\n name: \"adzen_inline_ad\",\n messageId,\n value,\n };\n }\n\n private makePlacementEvent(\n placement: AdzenPlacement,\n adId: string,\n ): Record<string, unknown> {\n return {\n type: \"CUSTOM_EVENT\",\n name: \"adzen_placement\",\n messageId: adId,\n value: {\n ...placement,\n message_id: placement.ad_id,\n },\n };\n }\n\n private mapPlacementRecord(\n record: PlacementRecord,\n messageId: string,\n ): AdzenPlacement {\n return {\n ad_id: String(record.ad_id),\n advertiser_name: \"\",\n headline: record.headline,\n cta_text: record.cta_text,\n destination_url: record.destination_url,\n adUnitPosition: record.placement || this.adUnitPosition,\n render_impression_url: record.tracking.impression_url,\n view_impression_url: record.tracking.impression_url,\n };\n }\n}\n"],"mappings":"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","names":["value"]}