@adzenai/ai 1.1.1 → 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 CHANGED
@@ -51,8 +51,8 @@ dispatchPlacementEvents(events);
51
51
  | Path | Environment | Provides |
52
52
  | --- | --- | --- |
53
53
  | `@adzenai/ai` | Any | Core type re-exports (`AdzenPlacement`, `ProcessResponse`, `ProcessResponseAd`) |
54
- | `@adzenai/ai/copilotkit` | Server or client | `AdzenAsyncMiddleware`, `AdzenAsyncConfig` |
55
- | `@adzenai/ai/copilotkit/react` | Browser (React 18+) | `AdzenCard`, `useAdzenPlacement`, `dispatchPlacementEvent(s)` |
54
+ | `@adzenai/ai/copilotkit` | Async: server or client. Stream: server only. | `AdzenAsyncMiddleware`, `AdzenAsyncConfig`, `AdzenStreamMiddleware`, `AdzenStreamConfig` |
55
+ | `@adzenai/ai/copilotkit/react` | Browser (React 18+) | `AdzenCard`, `InlineAd`, `useAdzenPlacement`, `useAdzenInlineAds`, `buildInlineAdSegments`, `dispatchPlacementEvent(s)` |
56
56
 
57
57
  ## How It Works
58
58
 
@@ -66,19 +66,67 @@ Impression beacons are fired as client-side GET requests directly to the deliver
66
66
 
67
67
  The middleware is **additive only** — it never modifies, blocks, or delays the original event stream. All failures degrade silently.
68
68
 
69
+ ## Streaming Enrichment
70
+
71
+ `AdzenStreamMiddleware` proxies the LLM response stream through Adzen's `/stream` endpoint for in-message ads, instead of waiting for the completed message. Tee the upstream response so one branch feeds AG-UI parsing and the other is piped to Adzen:
72
+
73
+ ```typescript
74
+ const [parseBranch, proxyBranch] = res.body!.tee();
75
+
76
+ for await (const evt of adzen.processStream(parseIntoAgUiEvents(parseBranch), {
77
+ prompt,
78
+ upstreamBody: proxyBranch,
79
+ })) {
80
+ sendToClient(evt);
81
+ }
82
+ ```
83
+
84
+ **Server-side only (Node 18+).** It passes a `ReadableStream` as the fetch request body; browsers other than Chromium do not support this and coerce the stream to the string `"[object ReadableStream]"`. The SDK feature-detects and emits an `adzen_error` rather than sending a corrupted body.
85
+
86
+ ### Rendering inline ads
87
+
88
+ Inline ads carry a `content_offset` — the number of message characters streamed before the ad arrived — so they render at that point in the text rather than at the end of the message. `getInlineAdSegments()` does the splitting:
89
+
90
+ ```tsx
91
+ const { getInlineAdSegments } = useAdzenInlineAds();
92
+
93
+ <div style={{ whiteSpace: "pre-wrap" }}>
94
+ {getInlineAdSegments(msg.id, msg.content).map((segment, i) =>
95
+ segment.kind === "text" ? (
96
+ <span key={i}>{segment.text}</span>
97
+ ) : (
98
+ <InlineAd key={segment.ad.ad_id} ad={segment.ad} />
99
+ ),
100
+ )}
101
+ </div>
102
+ ```
103
+
104
+ `InlineAd` renders as a plain text snippet — an "Ad" pill, the advertiser name, and the CTA as the only link (`[AD] Brooks Brothers clothing sale`) — and is block-level, so it appears on its own line directly below the text it follows. The advertiser text uses `creative.advertiser_name`, falling back to `creative.headline`.
105
+
106
+ Anchors are snapped forward to the next paragraph break, so segments are always whole paragraphs and each one can be passed to a Markdown renderer (`<Markdown>{segment.text}</Markdown>`) without a split breaking `**bold text**`, a list, or a code fence.
107
+
108
+ At most one ad is placed per paragraph break. Ads that would land on an already-used break move to the next free one (the end of the message counts as a slot), and any that still have nowhere to go are held back until the message grows, rather than stacking in one spot.
109
+
69
110
  ## Configuration
70
111
 
71
112
  ```typescript
72
113
  new AdzenAsyncMiddleware({
73
114
  apiKey: string; // required — Adzen API key
74
- endpointUrl?: string; // default: "https://api.adzen.ai/v1/ai"
115
+ endpointUrl?: string; // default: "https://api.adzen.ai/v1"
75
116
  timeoutMs?: number; // default: 3000
76
117
  adUnitPosition?: string; // default: "chin"
77
118
  location?: string; // optional — DMA location code for geo-targeting
78
119
  conversationId?: string; // optional — links impressions to conversation context
120
+ profileId?: string; // optional — sent as X-Profile-Id where APIM does not inject it
79
121
  });
80
122
  ```
81
123
 
124
+ `endpointUrl` is a base URL; the SDK appends `/process` and `/stream`. Include any environment-specific path prefix (such as `/ai` on stage and prod) in the value you pass.
125
+
126
+ `AdzenStreamMiddleware` accepts the same options plus `prefetch` (POST `/process` with the prompt before opening the stream), `placementPollIntervalMs`, and the `onInlineAd` / `onPlacement` / `onError` / `onStreamStart` / `onStreamEnd` callbacks.
127
+
128
+ The middleware never makes the assistant message wait on the ad pipeline: the prefetch is dispatched without being awaited, and content events are forwarded as they arrive whether or not an ad has been matched yet.
129
+
82
130
  ## License
83
131
 
84
132
  MIT
@@ -20,7 +20,8 @@ var __toCommonJS = (mod) => __copyProps(__defProp({}, "__esModule", { value: tru
20
20
  // src/copilotkit/index.ts
21
21
  var copilotkit_exports = {};
22
22
  __export(copilotkit_exports, {
23
- AdzenAsyncMiddleware: () => AdzenAsyncMiddleware
23
+ AdzenAsyncMiddleware: () => AdzenAsyncMiddleware,
24
+ AdzenStreamMiddleware: () => AdzenStreamMiddleware
24
25
  });
25
26
  module.exports = __toCommonJS(copilotkit_exports);
26
27
 
@@ -63,7 +64,7 @@ var AdzenAsyncMiddleware = class {
63
64
  constructor(config) {
64
65
  this.config = {
65
66
  ...config,
66
- endpointUrl: config.endpointUrl ?? "https://api.adzen.ai/v1/ai",
67
+ endpointUrl: config.endpointUrl ?? "https://api.adzen.ai/v1",
67
68
  timeoutMs: config.timeoutMs ?? 3e3,
68
69
  adUnitPosition: config.adUnitPosition ?? "chin"
69
70
  };
@@ -176,15 +177,17 @@ var AdzenAsyncMiddleware = class {
176
177
  };
177
178
  if (this.config.location) body.location = this.config.location;
178
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;
179
188
  const res = await fetch(`${this.config.endpointUrl}/process`, {
180
189
  method: "POST",
181
- headers: {
182
- "Content-Type": "application/json",
183
- "X-API-Key": this.config.apiKey,
184
- // AI-API requires an Idempotency-Key on /process (400 otherwise).
185
- // One per ad fetch — each completed message is a distinct request.
186
- "Idempotency-Key": generateIdempotencyKey()
187
- },
190
+ headers,
188
191
  body: JSON.stringify(body),
189
192
  signal: controller.signal
190
193
  });
@@ -229,8 +232,466 @@ var AdzenAsyncMiddleware = class {
229
232
  };
230
233
  }
231
234
  };
235
+
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
+ }
277
+ }
278
+ if (dataLines.length > 0) {
279
+ yield { event: currentEvent, data: dataLines.join("\n") };
280
+ }
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";
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);
395
+ }
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;
421
+ }
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
+ );
445
+ try {
446
+ const res = await fetch(`${this.endpointUrl}/stream`, {
447
+ method: "POST",
448
+ headers,
449
+ body,
450
+ signal: this.abortController?.signal,
451
+ // @ts-expect-error -- required for streaming request bodies (Node/undici)
452
+ duplex: "half"
453
+ });
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 }
479
+ });
480
+ if (!res.ok) return;
481
+ const data = await res.json();
482
+ for (const placement of data.placements) {
483
+ this.onPlacement?.(placement);
484
+ }
485
+ } catch {
486
+ }
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
+ };
232
692
  // Annotate the CommonJS export names for ESM import in node:
233
693
  0 && (module.exports = {
234
- AdzenAsyncMiddleware
694
+ AdzenAsyncMiddleware,
695
+ AdzenStreamMiddleware
235
696
  });
236
697
  //# sourceMappingURL=index.cjs.map