@adzenai/ai 1.0.0 → 1.1.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.
package/README.md CHANGED
@@ -21,6 +21,7 @@ import { AdzenAsyncMiddleware } from "@adzenai/ai/copilotkit";
21
21
 
22
22
  const adzen = new AdzenAsyncMiddleware({
23
23
  apiKey: process.env.ADZEN_API_KEY!,
24
+ location: process.env.ADZEN_LOCATION,
24
25
  });
25
26
 
26
27
  for await (const event of agentEventStream) {
@@ -49,7 +50,7 @@ dispatchPlacementEvents(events);
49
50
 
50
51
  | Path | Environment | Provides |
51
52
  | --- | --- | --- |
52
- | `@adzenai/ai` | Any | Core type re-exports (`AdzenPlacement`, `AdzenDecision`, `TrackingInfo`) |
53
+ | `@adzenai/ai` | Any | Core type re-exports (`AdzenPlacement`, `ProcessResponse`, `ProcessResponseAd`) |
53
54
  | `@adzenai/ai/copilotkit` | Server or client | `AdzenAsyncMiddleware`, `AdzenAsyncConfig` |
54
55
  | `@adzenai/ai/copilotkit/react` | Browser (React 18+) | `AdzenCard`, `useAdzenPlacement`, `dispatchPlacementEvent(s)` |
55
56
 
@@ -59,7 +60,9 @@ dispatchPlacementEvents(events);
59
60
  2. On message completion, it calls the Adzen `/process` API to match a contextual ad.
60
61
  3. If matched, an `adzen_placement` custom event is appended to the downstream stream.
61
62
  4. `dispatchPlacementEvents()` bridges these to browser `CustomEvent`s.
62
- 5. `AdzenCard` picks up the placement and renders it with automatic viewability tracking.
63
+ 5. `AdzenCard` picks up the placement and renders it with automatic impression tracking.
64
+
65
+ Impression beacons are fired as client-side GET requests directly to the delivery API — no server-side proxy needed.
63
66
 
64
67
  The middleware is **additive only** — it never modifies, blocks, or delays the original event stream. All failures degrade silently.
65
68
 
@@ -67,10 +70,12 @@ The middleware is **additive only** — it never modifies, blocks, or delays the
67
70
 
68
71
  ```typescript
69
72
  new AdzenAsyncMiddleware({
70
- apiKey: string; // required — Adzen API key
71
- endpointUrl?: string; // default: "https://api.adzen.ai/v1/ai"
72
- timeoutMs?: number; // default: 3000
73
- adUnitPosition?: string; // default: "chin"
73
+ apiKey: string; // required — Adzen API key
74
+ endpointUrl?: string; // default: "https://api.adzen.ai/v1/ai"
75
+ timeoutMs?: number; // default: 3000
76
+ adUnitPosition?: string; // default: "chin"
77
+ location?: string; // optional — DMA location code for geo-targeting
78
+ conversationId?: string; // optional — links impressions to conversation context
74
79
  });
75
80
  ```
76
81
 
@@ -20,12 +20,16 @@ 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,
24
- createImpressionHandler: () => createImpressionHandler
23
+ AdzenAsyncMiddleware: () => AdzenAsyncMiddleware
25
24
  });
26
25
  module.exports = __toCommonJS(copilotkit_exports);
27
26
 
28
27
  // src/copilotkit/middleware.ts
28
+ function generateIdempotencyKey() {
29
+ const globalCrypto = globalThis.crypto;
30
+ if (globalCrypto?.randomUUID) return globalCrypto.randomUUID();
31
+ return `adzen-${Date.now()}-${Math.random().toString(36).slice(2)}`;
32
+ }
29
33
  function isFinalOutputEvent(event) {
30
34
  const type = String(event.type ?? "");
31
35
  if (type === "RUN_FINISHED" || type === "RunFinished") return false;
@@ -55,6 +59,7 @@ var AdzenAsyncMiddleware = class {
55
59
  fetchedIds = /* @__PURE__ */ new Set();
56
60
  pendingFetches = [];
57
61
  fallbackCounter = 0;
62
+ conversationId;
58
63
  constructor(config) {
59
64
  this.config = {
60
65
  ...config,
@@ -62,6 +67,7 @@ var AdzenAsyncMiddleware = class {
62
67
  timeoutMs: config.timeoutMs ?? 3e3,
63
68
  adUnitPosition: config.adUnitPosition ?? "chin"
64
69
  };
70
+ this.conversationId = config.conversationId;
65
71
  }
66
72
  /**
67
73
  * Process a single AG-UI event. Returns an array of events to emit
@@ -71,6 +77,12 @@ var AdzenAsyncMiddleware = class {
71
77
  async processEvent(event) {
72
78
  const type = String(event.type ?? "");
73
79
  switch (type) {
80
+ case "RUN_STARTED":
81
+ case "RunStarted": {
82
+ const threadId = String(event.threadId ?? "");
83
+ if (threadId) this.conversationId = threadId;
84
+ return [event];
85
+ }
74
86
  case "TEXT_MESSAGE_START":
75
87
  case "TextMessageStart": {
76
88
  const messageId = this.resolveMessageId(event);
@@ -158,31 +170,56 @@ var AdzenAsyncMiddleware = class {
158
170
  const controller = new AbortController();
159
171
  const timeout = setTimeout(() => controller.abort(), this.config.timeoutMs);
160
172
  try {
173
+ const body = {
174
+ content,
175
+ message_id: messageId
176
+ };
177
+ if (this.config.location) body.location = this.config.location;
178
+ if (this.conversationId) body.conversation_id = this.conversationId;
161
179
  const res = await fetch(`${this.config.endpointUrl}/process`, {
162
180
  method: "POST",
163
181
  headers: {
164
182
  "Content-Type": "application/json",
165
- "X-API-Key": this.config.apiKey
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()
166
187
  },
167
- body: JSON.stringify({
168
- content,
169
- message_id: messageId
170
- }),
188
+ body: JSON.stringify(body),
171
189
  signal: controller.signal
172
190
  });
173
191
  if (!res.ok) return null;
174
192
  const data = await res.json();
175
- if (data.decision === "serve" && data.ad) return data.ad;
176
- return null;
193
+ const first = data.ads?.[0];
194
+ if (!first) return null;
195
+ return this.mapResponseToPlacement(first);
177
196
  } catch {
178
197
  return null;
179
198
  } finally {
180
199
  clearTimeout(timeout);
181
200
  }
182
201
  }
202
+ mapResponseToPlacement(ad) {
203
+ return {
204
+ ad_id: String(ad.ad_id),
205
+ advertiser_name: ad.advertiser_name,
206
+ advertiser_image_url: ad.sponsor_logo_url || void 0,
207
+ headline: ad.title,
208
+ description: ad.description,
209
+ cta_text: ad.cta,
210
+ destination_url: ad.click_through_url,
211
+ creative_url: ad.creative_url ?? void 0,
212
+ adUnitPosition: ad.placement ?? this.config.adUnitPosition,
213
+ render_impression_url: ad.render_impression_url,
214
+ view_impression_url: ad.view_impression_url
215
+ };
216
+ }
183
217
  makePlacementEvent(placement, messageId) {
184
218
  return {
185
- type: "CUSTOM_EVENT",
219
+ // AG-UI's event schema only accepts "CUSTOM"; @ag-ui/client zod-validates
220
+ // every SSE frame and aborts the run on an unknown type. "CUSTOM_EVENT"
221
+ // was rejected downstream.
222
+ type: "CUSTOM",
186
223
  name: "adzen_placement",
187
224
  messageId,
188
225
  value: {
@@ -192,73 +229,8 @@ var AdzenAsyncMiddleware = class {
192
229
  };
193
230
  }
194
231
  };
195
-
196
- // src/copilotkit/impressionHandler.ts
197
- var REQUIRED_FIELDS = [
198
- "type",
199
- "ad_id",
200
- "message_id",
201
- "ad_unit_position",
202
- "timestamp"
203
- ];
204
- function createImpressionHandler(config) {
205
- const endpointUrl = config.endpointUrl ?? "https://api.adzen.ai/v1/ai";
206
- return async (request) => {
207
- if (request.method !== "POST") {
208
- return new Response(JSON.stringify({ error: "Method not allowed" }), {
209
- status: 405,
210
- headers: { "Content-Type": "application/json" }
211
- });
212
- }
213
- let body;
214
- try {
215
- body = await request.json();
216
- } catch {
217
- return new Response(JSON.stringify({ error: "Invalid JSON" }), {
218
- status: 400,
219
- headers: { "Content-Type": "application/json" }
220
- });
221
- }
222
- for (const field of REQUIRED_FIELDS) {
223
- if (!body[field]) {
224
- return new Response(
225
- JSON.stringify({ error: `Missing required field: ${field}` }),
226
- { status: 400, headers: { "Content-Type": "application/json" } }
227
- );
228
- }
229
- }
230
- if (body.type !== "render" && body.type !== "view") {
231
- return new Response(
232
- JSON.stringify({ error: 'Invalid type: must be "render" or "view"' }),
233
- { status: 400, headers: { "Content-Type": "application/json" } }
234
- );
235
- }
236
- const upstreamPath = body.type === "render" ? "/impressions/render" : "/impressions/view";
237
- const { type: _type, ...forwardBody } = body;
238
- try {
239
- const upstream = await fetch(`${endpointUrl}${upstreamPath}`, {
240
- method: "POST",
241
- headers: {
242
- "Content-Type": "application/json",
243
- "X-API-Key": config.apiKey
244
- },
245
- body: JSON.stringify(forwardBody)
246
- });
247
- return new Response(upstream.body, {
248
- status: upstream.status,
249
- headers: { "Content-Type": "application/json" }
250
- });
251
- } catch {
252
- return new Response(
253
- JSON.stringify({ error: "Upstream request failed" }),
254
- { status: 502, headers: { "Content-Type": "application/json" } }
255
- );
256
- }
257
- };
258
- }
259
232
  // Annotate the CommonJS export names for ESM import in node:
260
233
  0 && (module.exports = {
261
- AdzenAsyncMiddleware,
262
- createImpressionHandler
234
+ AdzenAsyncMiddleware
263
235
  });
264
236
  //# sourceMappingURL=index.cjs.map
@@ -1 +1 @@
1
- {"version":3,"sources":["../../src/copilotkit/index.ts","../../src/copilotkit/middleware.ts","../../src/copilotkit/impressionHandler.ts"],"sourcesContent":["export { AdzenAsyncMiddleware } from \"./middleware.js\";\nexport type { AdzenAsyncConfig } from \"./middleware.js\";\nexport { createImpressionHandler } from \"./impressionHandler.js\";\nexport type { ImpressionHandlerConfig } from \"./impressionHandler.js\";\n","import type { AdzenDecision, AdzenPlacement } from \"@adzenai/core\";\n\nexport interface AdzenAsyncConfig {\n endpointUrl?: string;\n apiKey: string;\n timeoutMs?: number;\n adUnitPosition?: string;\n}\n\ninterface MessageBuffer {\n content: string;\n startedAt: number;\n fetched: boolean;\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\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 }\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 \"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 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 },\n body: JSON.stringify({\n content,\n message_id: messageId,\n }),\n signal: controller.signal,\n });\n\n if (!res.ok) return null;\n\n const data = (await res.json()) as AdzenDecision;\n if (data.decision === \"serve\" && data.ad) return data.ad;\n return null;\n } catch {\n return null;\n } finally {\n clearTimeout(timeout);\n }\n }\n\n private makePlacementEvent(\n placement: AdzenPlacement,\n messageId: string,\n ): Record<string, unknown> {\n return {\n type: \"CUSTOM_EVENT\",\n name: \"adzen_placement\",\n messageId,\n value: {\n ...placement,\n message_id: messageId,\n },\n };\n }\n}\n","export interface ImpressionHandlerConfig {\n apiKey: string;\n endpointUrl?: string;\n}\n\ninterface ImpressionRequestBody {\n type: \"render\" | \"view\";\n ad_id: string;\n message_id: string;\n ad_unit_position: string;\n timestamp: string;\n viewability_ms?: number;\n}\n\nconst REQUIRED_FIELDS: (keyof ImpressionRequestBody)[] = [\n \"type\",\n \"ad_id\",\n \"message_id\",\n \"ad_unit_position\",\n \"timestamp\",\n];\n\n/**\n * Creates a web-standard Request -> Response handler that proxies impression\n * beacons to the Adzen API with the publisher's API key. Mount this on your\n * server at any path (default convention: `/api/adzen/impressions`).\n *\n * Works in Next.js App Router, Remix, Hono, Express (via adapter),\n * Cloudflare Workers, and any framework supporting the Fetch API.\n */\nexport function createImpressionHandler(config: ImpressionHandlerConfig) {\n const endpointUrl = config.endpointUrl ?? \"https://api.adzen.ai/v1/ai\";\n\n return async (request: Request): Promise<Response> => {\n if (request.method !== \"POST\") {\n return new Response(JSON.stringify({ error: \"Method not allowed\" }), {\n status: 405,\n headers: { \"Content-Type\": \"application/json\" },\n });\n }\n\n let body: ImpressionRequestBody;\n try {\n body = (await request.json()) as ImpressionRequestBody;\n } catch {\n return new Response(JSON.stringify({ error: \"Invalid JSON\" }), {\n status: 400,\n headers: { \"Content-Type\": \"application/json\" },\n });\n }\n\n for (const field of REQUIRED_FIELDS) {\n if (!body[field]) {\n return new Response(\n JSON.stringify({ error: `Missing required field: ${field}` }),\n { status: 400, headers: { \"Content-Type\": \"application/json\" } },\n );\n }\n }\n\n if (body.type !== \"render\" && body.type !== \"view\") {\n return new Response(\n JSON.stringify({ error: 'Invalid type: must be \"render\" or \"view\"' }),\n { status: 400, headers: { \"Content-Type\": \"application/json\" } },\n );\n }\n\n const upstreamPath =\n body.type === \"render\" ? \"/impressions/render\" : \"/impressions/view\";\n\n const { type: _type, ...forwardBody } = body;\n\n try {\n const upstream = await fetch(`${endpointUrl}${upstreamPath}`, {\n method: \"POST\",\n headers: {\n \"Content-Type\": \"application/json\",\n \"X-API-Key\": config.apiKey,\n },\n body: JSON.stringify(forwardBody),\n });\n\n return new Response(upstream.body, {\n status: upstream.status,\n headers: { \"Content-Type\": \"application/json\" },\n });\n } catch {\n return new Response(\n JSON.stringify({ error: \"Upstream request failed\" }),\n { status: 502, headers: { \"Content-Type\": \"application/json\" } },\n );\n }\n };\n}\n"],"mappings":"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","names":[]}
1
+ {"version":3,"sources":["../../src/copilotkit/index.ts","../../src/copilotkit/middleware.ts"],"sourcesContent":["export { AdzenAsyncMiddleware } from \"./middleware.js\";\nexport type { AdzenAsyncConfig } from \"./middleware.js\";\n","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":";;;;;;;;;;;;;;;;;;;;AAAA;AAAA;AAAA;AAAA;AAAA;;;ACiBA,SAAS,yBAAiC;AACxC,QAAM,eAAgB,WACnB;AACH,MAAI,cAAc,WAAY,QAAO,aAAa,WAAW;AAC7D,SAAO,SAAS,KAAK,IAAI,CAAC,IAAI,KAAK,OAAO,EAAE,SAAS,EAAE,EAAE,MAAM,CAAC,CAAC;AACnE;AAEA,SAAS,mBAAmB,OAAyC;AACnE,QAAM,OAAO,OAAO,MAAM,QAAQ,EAAE;AACpC,MAAI,SAAS,kBAAkB,SAAS,cAAe,QAAO;AAC9D,MAAI,SAAS,YAAY,SAAS,kBAAkB,SAAS;AAC3D,WAAO;AACT,MAAI,MAAM,UAAU,QAAQ,MAAM,YAAY,KAAM,QAAO;AAC3D,QAAM,OAAO,OAAO,MAAM,QAAQ,MAAM,SAAS,EAAE,EAAE,YAAY;AACjE,SAAO,KAAK,SAAS,iBAAiB,KAAK,KAAK,SAAS,cAAc;AACzE;AAEA,SAAS,wBAAwB,OAA+C;AAC9E,aAAW,OAAO,CAAC,UAAU,UAAU,QAAQ,WAAW,SAAS,GAAG;AACpE,UAAM,MAAM,MAAM,GAAG;AACrB,QAAI,OAAO,QAAQ,YAAY,IAAI,SAAS,EAAG,QAAO;AACtD,QAAI,OAAO,QAAQ,YAAY,QAAQ,MAAM;AAC3C,UAAI;AACF,eAAO,KAAK,UAAU,GAAG;AAAA,MAC3B,QAAQ;AACN;AAAA,MACF;AAAA,IACF;AAAA,EACF;AACA,SAAO;AACT;AAWO,IAAM,uBAAN,MAA2B;AAAA,EACxB;AAAA,EAEA,UAAU,oBAAI,IAA2B;AAAA,EACzC,aAAa,oBAAI,IAAY;AAAA,EAC7B,iBAAkC,CAAC;AAAA,EACnC,kBAAkB;AAAA,EAClB;AAAA,EAER,YAAY,QAA0B;AACpC,SAAK,SAAS;AAAA,MACZ,GAAG;AAAA,MACH,aAAa,OAAO,eAAe;AAAA,MACnC,WAAW,OAAO,aAAa;AAAA,MAC/B,gBAAgB,OAAO,kBAAkB;AAAA,IAC3C;AACA,SAAK,iBAAiB,OAAO;AAAA,EAC/B;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA,EAOA,MAAM,aAAa,OAAoE;AACrF,UAAM,OAAO,OAAO,MAAM,QAAQ,EAAE;AAEpC,YAAQ,MAAM;AAAA,MACZ,KAAK;AAAA,MACL,KAAK,cAAc;AACjB,cAAM,WAAW,OAAO,MAAM,YAAY,EAAE;AAC5C,YAAI,SAAU,MAAK,iBAAiB;AACpC,eAAO,CAAC,KAAK;AAAA,MACf;AAAA,MAEA,KAAK;AAAA,MACL,KAAK,oBAAoB;AACvB,cAAM,YAAY,KAAK,iBAAiB,KAAK;AAC7C,aAAK,kBAAkB;AACvB,aAAK,QAAQ,IAAI,WAAW;AAAA,UAC1B,SAAS;AAAA,UACT,WAAW,KAAK,IAAI;AAAA,UACpB,SAAS;AAAA,QACX,CAAC;AACD,eAAO,CAAC,KAAK;AAAA,MACf;AAAA,MAEA,KAAK;AAAA,MACL,KAAK,sBAAsB;AACzB,cAAM,YAAY,OAAO,MAAM,aAAa,EAAE;AAC9C,cAAM,MAAM,KAAK,QAAQ,IAAI,SAAS;AACtC,YAAI,IAAK,KAAI,WAAW,OAAO,MAAM,SAAS,EAAE;AAChD,eAAO,CAAC,KAAK;AAAA,MACf;AAAA,MAEA,KAAK;AAAA,MACL,KAAK,kBAAkB;AACrB,cAAM,YAAY,OAAO,MAAM,aAAa,EAAE;AAC9C,cAAM,MAAM,KAAK,QAAQ,IAAI,SAAS;AACtC,cAAM,MAAiC,CAAC,KAAK;AAE7C,YAAI,OAAO,CAAC,IAAI,SAAS;AACvB,cAAI,UAAU;AACd,eAAK,WAAW,IAAI,SAAS;AAC7B,gBAAM,eAAe,KAAK,QAAQ,IAAI,SAAS,SAAS,EAAE;AAAA,YACxD,CAAC,cAAc;AACb,kBAAI,UAAW,KAAI,KAAK,KAAK,mBAAmB,WAAW,SAAS,CAAC;AAAA,YACvE;AAAA,UACF;AACA,eAAK,eAAe,KAAK,YAAY;AACrC,gBAAM;AACN,eAAK,QAAQ,OAAO,SAAS;AAAA,QAC/B;AAEA,eAAO;AAAA,MACT;AAAA,MAEA,KAAK;AAAA,MACL,KAAK;AAAA,MACL,KAAK,eAAe;AAClB,YAAI,mBAAmB,KAAK,GAAG;AAC7B,gBAAM,YAAY,OAAO,MAAM,aAAa,EAAE;AAC9C,cAAI,CAAC,KAAK,WAAW,IAAI,SAAS,GAAG;AACnC,kBAAM,MAAM,KAAK,QAAQ,IAAI,SAAS;AACtC,kBAAM,UAAU,wBAAwB,KAAK;AAC7C,gBAAI,SAAS;AACX,oBAAM,MAAiC,CAAC,KAAK;AAC7C,kBAAI,IAAK,KAAI,UAAU;AACvB,mBAAK,WAAW,IAAI,SAAS;AAC7B,oBAAM,eAAe,KAAK,QAAQ,SAAS,SAAS,EAAE;AAAA,gBACpD,CAAC,cAAc;AACb,sBAAI,UAAW,KAAI,KAAK,KAAK,mBAAmB,WAAW,SAAS,CAAC;AAAA,gBACvE;AAAA,cACF;AACA,mBAAK,eAAe,KAAK,YAAY;AACrC,oBAAM;AACN,qBAAO;AAAA,YACT;AAAA,UACF;AAAA,QACF;AACA,eAAO,CAAC,KAAK;AAAA,MACf;AAAA,MAEA,KAAK;AAAA,MACL,KAAK,eAAe;AAClB,cAAM,QAAQ,WAAW,KAAK,cAAc;AAC5C,aAAK,iBAAiB,CAAC;AACvB,eAAO,CAAC,KAAK;AAAA,MACf;AAAA,MAEA;AACE,eAAO,CAAC,KAAK;AAAA,IACjB;AAAA,EACF;AAAA,EAEQ,iBAAiB,OAAwC;AAC/D,UAAM,KAAK,OAAO,MAAM,aAAa,EAAE;AACvC,QAAI,GAAI,QAAO;AACf,WAAO,oBAAoB,KAAK,IAAI,CAAC,IAAI,KAAK,iBAAiB;AAAA,EACjE;AAAA,EAEQ,oBAA0B;AAChC,UAAM,SAAS,KAAK,IAAI,IAAI,KAAK,OAAO,YAAY;AACpD,eAAW,CAAC,KAAK,GAAG,KAAK,KAAK,SAAS;AACrC,UAAI,IAAI,YAAY,OAAQ,MAAK,QAAQ,OAAO,GAAG;AAAA,IACrD;AAAA,EACF;AAAA,EAEA,MAAc,QACZ,SACA,WACgC;AAChC,UAAM,aAAa,IAAI,gBAAgB;AACvC,UAAM,UAAU,WAAW,MAAM,WAAW,MAAM,GAAG,KAAK,OAAO,SAAS;AAE1E,QAAI;AACF,YAAM,OAAgC;AAAA,QACpC;AAAA,QACA,YAAY;AAAA,MACd;AACA,UAAI,KAAK,OAAO,SAAU,MAAK,WAAW,KAAK,OAAO;AACtD,UAAI,KAAK,eAAgB,MAAK,kBAAkB,KAAK;AAErD,YAAM,MAAM,MAAM,MAAM,GAAG,KAAK,OAAO,WAAW,YAAY;AAAA,QAC5D,QAAQ;AAAA,QACR,SAAS;AAAA,UACP,gBAAgB;AAAA,UAChB,aAAa,KAAK,OAAO;AAAA;AAAA;AAAA,UAGzB,mBAAmB,uBAAuB;AAAA,QAC5C;AAAA,QACA,MAAM,KAAK,UAAU,IAAI;AAAA,QACzB,QAAQ,WAAW;AAAA,MACrB,CAAC;AAED,UAAI,CAAC,IAAI,GAAI,QAAO;AAEpB,YAAM,OAAQ,MAAM,IAAI,KAAK;AAC7B,YAAM,QAAQ,KAAK,MAAM,CAAC;AAC1B,UAAI,CAAC,MAAO,QAAO;AACnB,aAAO,KAAK,uBAAuB,KAAK;AAAA,IAC1C,QAAQ;AACN,aAAO;AAAA,IACT,UAAE;AACA,mBAAa,OAAO;AAAA,IACtB;AAAA,EACF;AAAA,EAEQ,uBAAuB,IAAuC;AACpE,WAAO;AAAA,MACL,OAAO,OAAO,GAAG,KAAK;AAAA,MACtB,iBAAiB,GAAG;AAAA,MACpB,sBAAsB,GAAG,oBAAoB;AAAA,MAC7C,UAAU,GAAG;AAAA,MACb,aAAa,GAAG;AAAA,MAChB,UAAU,GAAG;AAAA,MACb,iBAAiB,GAAG;AAAA,MACpB,cAAc,GAAG,gBAAgB;AAAA,MACjC,gBAAgB,GAAG,aAAa,KAAK,OAAO;AAAA,MAC5C,uBAAuB,GAAG;AAAA,MAC1B,qBAAqB,GAAG;AAAA,IAC1B;AAAA,EACF;AAAA,EAEQ,mBACN,WACA,WACyB;AACzB,WAAO;AAAA;AAAA;AAAA;AAAA,MAIL,MAAM;AAAA,MACN,MAAM;AAAA,MACN;AAAA,MACA,OAAO;AAAA,QACL,GAAG;AAAA,QACH,YAAY;AAAA,MACd;AAAA,IACF;AAAA,EACF;AACF;","names":[]}
@@ -3,6 +3,8 @@ interface AdzenAsyncConfig {
3
3
  apiKey: string;
4
4
  timeoutMs?: number;
5
5
  adUnitPosition?: string;
6
+ location?: string;
7
+ conversationId?: string;
6
8
  }
7
9
  /**
8
10
  * AG-UI middleware that intercepts the event stream and calls AI-API after
@@ -19,6 +21,7 @@ declare class AdzenAsyncMiddleware {
19
21
  private fetchedIds;
20
22
  private pendingFetches;
21
23
  private fallbackCounter;
24
+ private conversationId;
22
25
  constructor(config: AdzenAsyncConfig);
23
26
  /**
24
27
  * Process a single AG-UI event. Returns an array of events to emit
@@ -29,21 +32,8 @@ declare class AdzenAsyncMiddleware {
29
32
  private resolveMessageId;
30
33
  private evictStaleBuffers;
31
34
  private fetchAd;
35
+ private mapResponseToPlacement;
32
36
  private makePlacementEvent;
33
37
  }
34
38
 
35
- interface ImpressionHandlerConfig {
36
- apiKey: string;
37
- endpointUrl?: string;
38
- }
39
- /**
40
- * Creates a web-standard Request -> Response handler that proxies impression
41
- * beacons to the Adzen API with the publisher's API key. Mount this on your
42
- * server at any path (default convention: `/api/adzen/impressions`).
43
- *
44
- * Works in Next.js App Router, Remix, Hono, Express (via adapter),
45
- * Cloudflare Workers, and any framework supporting the Fetch API.
46
- */
47
- declare function createImpressionHandler(config: ImpressionHandlerConfig): (request: Request) => Promise<Response>;
48
-
49
- export { type AdzenAsyncConfig, AdzenAsyncMiddleware, type ImpressionHandlerConfig, createImpressionHandler };
39
+ export { type AdzenAsyncConfig, AdzenAsyncMiddleware };
@@ -3,6 +3,8 @@ interface AdzenAsyncConfig {
3
3
  apiKey: string;
4
4
  timeoutMs?: number;
5
5
  adUnitPosition?: string;
6
+ location?: string;
7
+ conversationId?: string;
6
8
  }
7
9
  /**
8
10
  * AG-UI middleware that intercepts the event stream and calls AI-API after
@@ -19,6 +21,7 @@ declare class AdzenAsyncMiddleware {
19
21
  private fetchedIds;
20
22
  private pendingFetches;
21
23
  private fallbackCounter;
24
+ private conversationId;
22
25
  constructor(config: AdzenAsyncConfig);
23
26
  /**
24
27
  * Process a single AG-UI event. Returns an array of events to emit
@@ -29,21 +32,8 @@ declare class AdzenAsyncMiddleware {
29
32
  private resolveMessageId;
30
33
  private evictStaleBuffers;
31
34
  private fetchAd;
35
+ private mapResponseToPlacement;
32
36
  private makePlacementEvent;
33
37
  }
34
38
 
35
- interface ImpressionHandlerConfig {
36
- apiKey: string;
37
- endpointUrl?: string;
38
- }
39
- /**
40
- * Creates a web-standard Request -> Response handler that proxies impression
41
- * beacons to the Adzen API with the publisher's API key. Mount this on your
42
- * server at any path (default convention: `/api/adzen/impressions`).
43
- *
44
- * Works in Next.js App Router, Remix, Hono, Express (via adapter),
45
- * Cloudflare Workers, and any framework supporting the Fetch API.
46
- */
47
- declare function createImpressionHandler(config: ImpressionHandlerConfig): (request: Request) => Promise<Response>;
48
-
49
- export { type AdzenAsyncConfig, AdzenAsyncMiddleware, type ImpressionHandlerConfig, createImpressionHandler };
39
+ export { type AdzenAsyncConfig, AdzenAsyncMiddleware };
@@ -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,6 +33,7 @@ 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,
@@ -35,6 +41,7 @@ var AdzenAsyncMiddleware = class {
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,56 @@ 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;
134
153
  const res = await fetch(`${this.config.endpointUrl}/process`, {
135
154
  method: "POST",
136
155
  headers: {
137
156
  "Content-Type": "application/json",
138
- "X-API-Key": this.config.apiKey
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()
139
161
  },
140
- body: JSON.stringify({
141
- content,
142
- message_id: messageId
143
- }),
162
+ body: JSON.stringify(body),
144
163
  signal: controller.signal
145
164
  });
146
165
  if (!res.ok) return null;
147
166
  const data = await res.json();
148
- if (data.decision === "serve" && data.ad) return data.ad;
149
- return null;
167
+ const first = data.ads?.[0];
168
+ if (!first) return null;
169
+ return this.mapResponseToPlacement(first);
150
170
  } catch {
151
171
  return null;
152
172
  } finally {
153
173
  clearTimeout(timeout);
154
174
  }
155
175
  }
176
+ mapResponseToPlacement(ad) {
177
+ return {
178
+ ad_id: String(ad.ad_id),
179
+ advertiser_name: ad.advertiser_name,
180
+ advertiser_image_url: ad.sponsor_logo_url || void 0,
181
+ headline: ad.title,
182
+ description: ad.description,
183
+ cta_text: ad.cta,
184
+ destination_url: ad.click_through_url,
185
+ creative_url: ad.creative_url ?? void 0,
186
+ adUnitPosition: ad.placement ?? this.config.adUnitPosition,
187
+ render_impression_url: ad.render_impression_url,
188
+ view_impression_url: ad.view_impression_url
189
+ };
190
+ }
156
191
  makePlacementEvent(placement, messageId) {
157
192
  return {
158
- type: "CUSTOM_EVENT",
193
+ // AG-UI's event schema only accepts "CUSTOM"; @ag-ui/client zod-validates
194
+ // every SSE frame and aborts the run on an unknown type. "CUSTOM_EVENT"
195
+ // was rejected downstream.
196
+ type: "CUSTOM",
159
197
  name: "adzen_placement",
160
198
  messageId,
161
199
  value: {
@@ -165,72 +203,7 @@ var AdzenAsyncMiddleware = class {
165
203
  };
166
204
  }
167
205
  };
168
-
169
- // src/copilotkit/impressionHandler.ts
170
- var REQUIRED_FIELDS = [
171
- "type",
172
- "ad_id",
173
- "message_id",
174
- "ad_unit_position",
175
- "timestamp"
176
- ];
177
- function createImpressionHandler(config) {
178
- const endpointUrl = config.endpointUrl ?? "https://api.adzen.ai/v1/ai";
179
- return async (request) => {
180
- if (request.method !== "POST") {
181
- return new Response(JSON.stringify({ error: "Method not allowed" }), {
182
- status: 405,
183
- headers: { "Content-Type": "application/json" }
184
- });
185
- }
186
- let body;
187
- try {
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
- });
194
- }
195
- for (const field of REQUIRED_FIELDS) {
196
- if (!body[field]) {
197
- return new Response(
198
- JSON.stringify({ error: `Missing required field: ${field}` }),
199
- { status: 400, headers: { "Content-Type": "application/json" } }
200
- );
201
- }
202
- }
203
- if (body.type !== "render" && body.type !== "view") {
204
- return new Response(
205
- JSON.stringify({ error: 'Invalid type: must be "render" or "view"' }),
206
- { status: 400, headers: { "Content-Type": "application/json" } }
207
- );
208
- }
209
- const upstreamPath = body.type === "render" ? "/impressions/render" : "/impressions/view";
210
- const { type: _type, ...forwardBody } = body;
211
- try {
212
- const upstream = await fetch(`${endpointUrl}${upstreamPath}`, {
213
- method: "POST",
214
- headers: {
215
- "Content-Type": "application/json",
216
- "X-API-Key": config.apiKey
217
- },
218
- body: JSON.stringify(forwardBody)
219
- });
220
- return new Response(upstream.body, {
221
- status: upstream.status,
222
- headers: { "Content-Type": "application/json" }
223
- });
224
- } catch {
225
- return new Response(
226
- JSON.stringify({ error: "Upstream request failed" }),
227
- { status: 502, headers: { "Content-Type": "application/json" } }
228
- );
229
- }
230
- };
231
- }
232
206
  export {
233
- AdzenAsyncMiddleware,
234
- createImpressionHandler
207
+ AdzenAsyncMiddleware
235
208
  };
236
209
  //# sourceMappingURL=index.js.map
@@ -1 +1 @@
1
- {"version":3,"sources":["../../src/copilotkit/middleware.ts","../../src/copilotkit/impressionHandler.ts"],"sourcesContent":["import type { AdzenDecision, AdzenPlacement } from \"@adzenai/core\";\n\nexport interface AdzenAsyncConfig {\n endpointUrl?: string;\n apiKey: string;\n timeoutMs?: number;\n adUnitPosition?: string;\n}\n\ninterface MessageBuffer {\n content: string;\n startedAt: number;\n fetched: boolean;\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\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 }\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 \"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 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 },\n body: JSON.stringify({\n content,\n message_id: messageId,\n }),\n signal: controller.signal,\n });\n\n if (!res.ok) return null;\n\n const data = (await res.json()) as AdzenDecision;\n if (data.decision === \"serve\" && data.ad) return data.ad;\n return null;\n } catch {\n return null;\n } finally {\n clearTimeout(timeout);\n }\n }\n\n private makePlacementEvent(\n placement: AdzenPlacement,\n messageId: string,\n ): Record<string, unknown> {\n return {\n type: \"CUSTOM_EVENT\",\n name: \"adzen_placement\",\n messageId,\n value: {\n ...placement,\n message_id: messageId,\n },\n };\n }\n}\n","export interface ImpressionHandlerConfig {\n apiKey: string;\n endpointUrl?: string;\n}\n\ninterface ImpressionRequestBody {\n type: \"render\" | \"view\";\n ad_id: string;\n message_id: string;\n ad_unit_position: string;\n timestamp: string;\n viewability_ms?: number;\n}\n\nconst REQUIRED_FIELDS: (keyof ImpressionRequestBody)[] = [\n \"type\",\n \"ad_id\",\n \"message_id\",\n \"ad_unit_position\",\n \"timestamp\",\n];\n\n/**\n * Creates a web-standard Request -> Response handler that proxies impression\n * beacons to the Adzen API with the publisher's API key. Mount this on your\n * server at any path (default convention: `/api/adzen/impressions`).\n *\n * Works in Next.js App Router, Remix, Hono, Express (via adapter),\n * Cloudflare Workers, and any framework supporting the Fetch API.\n */\nexport function createImpressionHandler(config: ImpressionHandlerConfig) {\n const endpointUrl = config.endpointUrl ?? \"https://api.adzen.ai/v1/ai\";\n\n return async (request: Request): Promise<Response> => {\n if (request.method !== \"POST\") {\n return new Response(JSON.stringify({ error: \"Method not allowed\" }), {\n status: 405,\n headers: { \"Content-Type\": \"application/json\" },\n });\n }\n\n let body: ImpressionRequestBody;\n try {\n body = (await request.json()) as ImpressionRequestBody;\n } catch {\n return new Response(JSON.stringify({ error: \"Invalid JSON\" }), {\n status: 400,\n headers: { \"Content-Type\": \"application/json\" },\n });\n }\n\n for (const field of REQUIRED_FIELDS) {\n if (!body[field]) {\n return new Response(\n JSON.stringify({ error: `Missing required field: ${field}` }),\n { status: 400, headers: { \"Content-Type\": \"application/json\" } },\n );\n }\n }\n\n if (body.type !== \"render\" && body.type !== \"view\") {\n return new Response(\n JSON.stringify({ error: 'Invalid type: must be \"render\" or \"view\"' }),\n { status: 400, headers: { \"Content-Type\": \"application/json\" } },\n );\n }\n\n const upstreamPath =\n body.type === \"render\" ? \"/impressions/render\" : \"/impressions/view\";\n\n const { type: _type, ...forwardBody } = body;\n\n try {\n const upstream = await fetch(`${endpointUrl}${upstreamPath}`, {\n method: \"POST\",\n headers: {\n \"Content-Type\": \"application/json\",\n \"X-API-Key\": config.apiKey,\n },\n body: JSON.stringify(forwardBody),\n });\n\n return new Response(upstream.body, {\n status: upstream.status,\n headers: { \"Content-Type\": \"application/json\" },\n });\n } catch {\n return new Response(\n JSON.stringify({ error: \"Upstream request failed\" }),\n { status: 502, headers: { \"Content-Type\": \"application/json\" } },\n );\n }\n };\n}\n"],"mappings":";AAeA,SAAS,mBAAmB,OAAyC;AACnE,QAAM,OAAO,OAAO,MAAM,QAAQ,EAAE;AACpC,MAAI,SAAS,kBAAkB,SAAS,cAAe,QAAO;AAC9D,MAAI,SAAS,YAAY,SAAS,kBAAkB,SAAS;AAC3D,WAAO;AACT,MAAI,MAAM,UAAU,QAAQ,MAAM,YAAY,KAAM,QAAO;AAC3D,QAAM,OAAO,OAAO,MAAM,QAAQ,MAAM,SAAS,EAAE,EAAE,YAAY;AACjE,SAAO,KAAK,SAAS,iBAAiB,KAAK,KAAK,SAAS,cAAc;AACzE;AAEA,SAAS,wBAAwB,OAA+C;AAC9E,aAAW,OAAO,CAAC,UAAU,UAAU,QAAQ,WAAW,SAAS,GAAG;AACpE,UAAM,MAAM,MAAM,GAAG;AACrB,QAAI,OAAO,QAAQ,YAAY,IAAI,SAAS,EAAG,QAAO;AACtD,QAAI,OAAO,QAAQ,YAAY,QAAQ,MAAM;AAC3C,UAAI;AACF,eAAO,KAAK,UAAU,GAAG;AAAA,MAC3B,QAAQ;AACN;AAAA,MACF;AAAA,IACF;AAAA,EACF;AACA,SAAO;AACT;AAWO,IAAM,uBAAN,MAA2B;AAAA,EACxB;AAAA,EAEA,UAAU,oBAAI,IAA2B;AAAA,EACzC,aAAa,oBAAI,IAAY;AAAA,EAC7B,iBAAkC,CAAC;AAAA,EACnC,kBAAkB;AAAA,EAE1B,YAAY,QAA0B;AACpC,SAAK,SAAS;AAAA,MACZ,GAAG;AAAA,MACH,aAAa,OAAO,eAAe;AAAA,MACnC,WAAW,OAAO,aAAa;AAAA,MAC/B,gBAAgB,OAAO,kBAAkB;AAAA,IAC3C;AAAA,EACF;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA,EAOA,MAAM,aAAa,OAAoE;AACrF,UAAM,OAAO,OAAO,MAAM,QAAQ,EAAE;AAEpC,YAAQ,MAAM;AAAA,MACZ,KAAK;AAAA,MACL,KAAK,oBAAoB;AACvB,cAAM,YAAY,KAAK,iBAAiB,KAAK;AAC7C,aAAK,kBAAkB;AACvB,aAAK,QAAQ,IAAI,WAAW;AAAA,UAC1B,SAAS;AAAA,UACT,WAAW,KAAK,IAAI;AAAA,UACpB,SAAS;AAAA,QACX,CAAC;AACD,eAAO,CAAC,KAAK;AAAA,MACf;AAAA,MAEA,KAAK;AAAA,MACL,KAAK,sBAAsB;AACzB,cAAM,YAAY,OAAO,MAAM,aAAa,EAAE;AAC9C,cAAM,MAAM,KAAK,QAAQ,IAAI,SAAS;AACtC,YAAI,IAAK,KAAI,WAAW,OAAO,MAAM,SAAS,EAAE;AAChD,eAAO,CAAC,KAAK;AAAA,MACf;AAAA,MAEA,KAAK;AAAA,MACL,KAAK,kBAAkB;AACrB,cAAM,YAAY,OAAO,MAAM,aAAa,EAAE;AAC9C,cAAM,MAAM,KAAK,QAAQ,IAAI,SAAS;AACtC,cAAM,MAAiC,CAAC,KAAK;AAE7C,YAAI,OAAO,CAAC,IAAI,SAAS;AACvB,cAAI,UAAU;AACd,eAAK,WAAW,IAAI,SAAS;AAC7B,gBAAM,eAAe,KAAK,QAAQ,IAAI,SAAS,SAAS,EAAE;AAAA,YACxD,CAAC,cAAc;AACb,kBAAI,UAAW,KAAI,KAAK,KAAK,mBAAmB,WAAW,SAAS,CAAC;AAAA,YACvE;AAAA,UACF;AACA,eAAK,eAAe,KAAK,YAAY;AACrC,gBAAM;AACN,eAAK,QAAQ,OAAO,SAAS;AAAA,QAC/B;AAEA,eAAO;AAAA,MACT;AAAA,MAEA,KAAK;AAAA,MACL,KAAK;AAAA,MACL,KAAK,eAAe;AAClB,YAAI,mBAAmB,KAAK,GAAG;AAC7B,gBAAM,YAAY,OAAO,MAAM,aAAa,EAAE;AAC9C,cAAI,CAAC,KAAK,WAAW,IAAI,SAAS,GAAG;AACnC,kBAAM,MAAM,KAAK,QAAQ,IAAI,SAAS;AACtC,kBAAM,UAAU,wBAAwB,KAAK;AAC7C,gBAAI,SAAS;AACX,oBAAM,MAAiC,CAAC,KAAK;AAC7C,kBAAI,IAAK,KAAI,UAAU;AACvB,mBAAK,WAAW,IAAI,SAAS;AAC7B,oBAAM,eAAe,KAAK,QAAQ,SAAS,SAAS,EAAE;AAAA,gBACpD,CAAC,cAAc;AACb,sBAAI,UAAW,KAAI,KAAK,KAAK,mBAAmB,WAAW,SAAS,CAAC;AAAA,gBACvE;AAAA,cACF;AACA,mBAAK,eAAe,KAAK,YAAY;AACrC,oBAAM;AACN,qBAAO;AAAA,YACT;AAAA,UACF;AAAA,QACF;AACA,eAAO,CAAC,KAAK;AAAA,MACf;AAAA,MAEA,KAAK;AAAA,MACL,KAAK,eAAe;AAClB,cAAM,QAAQ,WAAW,KAAK,cAAc;AAC5C,aAAK,iBAAiB,CAAC;AACvB,eAAO,CAAC,KAAK;AAAA,MACf;AAAA,MAEA;AACE,eAAO,CAAC,KAAK;AAAA,IACjB;AAAA,EACF;AAAA,EAEQ,iBAAiB,OAAwC;AAC/D,UAAM,KAAK,OAAO,MAAM,aAAa,EAAE;AACvC,QAAI,GAAI,QAAO;AACf,WAAO,oBAAoB,KAAK,IAAI,CAAC,IAAI,KAAK,iBAAiB;AAAA,EACjE;AAAA,EAEQ,oBAA0B;AAChC,UAAM,SAAS,KAAK,IAAI,IAAI,KAAK,OAAO,YAAY;AACpD,eAAW,CAAC,KAAK,GAAG,KAAK,KAAK,SAAS;AACrC,UAAI,IAAI,YAAY,OAAQ,MAAK,QAAQ,OAAO,GAAG;AAAA,IACrD;AAAA,EACF;AAAA,EAEA,MAAc,QACZ,SACA,WACgC;AAChC,UAAM,aAAa,IAAI,gBAAgB;AACvC,UAAM,UAAU,WAAW,MAAM,WAAW,MAAM,GAAG,KAAK,OAAO,SAAS;AAE1E,QAAI;AACF,YAAM,MAAM,MAAM,MAAM,GAAG,KAAK,OAAO,WAAW,YAAY;AAAA,QAC5D,QAAQ;AAAA,QACR,SAAS;AAAA,UACP,gBAAgB;AAAA,UAChB,aAAa,KAAK,OAAO;AAAA,QAC3B;AAAA,QACA,MAAM,KAAK,UAAU;AAAA,UACnB;AAAA,UACA,YAAY;AAAA,QACd,CAAC;AAAA,QACD,QAAQ,WAAW;AAAA,MACrB,CAAC;AAED,UAAI,CAAC,IAAI,GAAI,QAAO;AAEpB,YAAM,OAAQ,MAAM,IAAI,KAAK;AAC7B,UAAI,KAAK,aAAa,WAAW,KAAK,GAAI,QAAO,KAAK;AACtD,aAAO;AAAA,IACT,QAAQ;AACN,aAAO;AAAA,IACT,UAAE;AACA,mBAAa,OAAO;AAAA,IACtB;AAAA,EACF;AAAA,EAEQ,mBACN,WACA,WACyB;AACzB,WAAO;AAAA,MACL,MAAM;AAAA,MACN,MAAM;AAAA,MACN;AAAA,MACA,OAAO;AAAA,QACL,GAAG;AAAA,QACH,YAAY;AAAA,MACd;AAAA,IACF;AAAA,EACF;AACF;;;ACzMA,IAAM,kBAAmD;AAAA,EACvD;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AACF;AAUO,SAAS,wBAAwB,QAAiC;AACvE,QAAM,cAAc,OAAO,eAAe;AAE1C,SAAO,OAAO,YAAwC;AACpD,QAAI,QAAQ,WAAW,QAAQ;AAC7B,aAAO,IAAI,SAAS,KAAK,UAAU,EAAE,OAAO,qBAAqB,CAAC,GAAG;AAAA,QACnE,QAAQ;AAAA,QACR,SAAS,EAAE,gBAAgB,mBAAmB;AAAA,MAChD,CAAC;AAAA,IACH;AAEA,QAAI;AACJ,QAAI;AACF,aAAQ,MAAM,QAAQ,KAAK;AAAA,IAC7B,QAAQ;AACN,aAAO,IAAI,SAAS,KAAK,UAAU,EAAE,OAAO,eAAe,CAAC,GAAG;AAAA,QAC7D,QAAQ;AAAA,QACR,SAAS,EAAE,gBAAgB,mBAAmB;AAAA,MAChD,CAAC;AAAA,IACH;AAEA,eAAW,SAAS,iBAAiB;AACnC,UAAI,CAAC,KAAK,KAAK,GAAG;AAChB,eAAO,IAAI;AAAA,UACT,KAAK,UAAU,EAAE,OAAO,2BAA2B,KAAK,GAAG,CAAC;AAAA,UAC5D,EAAE,QAAQ,KAAK,SAAS,EAAE,gBAAgB,mBAAmB,EAAE;AAAA,QACjE;AAAA,MACF;AAAA,IACF;AAEA,QAAI,KAAK,SAAS,YAAY,KAAK,SAAS,QAAQ;AAClD,aAAO,IAAI;AAAA,QACT,KAAK,UAAU,EAAE,OAAO,2CAA2C,CAAC;AAAA,QACpE,EAAE,QAAQ,KAAK,SAAS,EAAE,gBAAgB,mBAAmB,EAAE;AAAA,MACjE;AAAA,IACF;AAEA,UAAM,eACJ,KAAK,SAAS,WAAW,wBAAwB;AAEnD,UAAM,EAAE,MAAM,OAAO,GAAG,YAAY,IAAI;AAExC,QAAI;AACF,YAAM,WAAW,MAAM,MAAM,GAAG,WAAW,GAAG,YAAY,IAAI;AAAA,QAC5D,QAAQ;AAAA,QACR,SAAS;AAAA,UACP,gBAAgB;AAAA,UAChB,aAAa,OAAO;AAAA,QACtB;AAAA,QACA,MAAM,KAAK,UAAU,WAAW;AAAA,MAClC,CAAC;AAED,aAAO,IAAI,SAAS,SAAS,MAAM;AAAA,QACjC,QAAQ,SAAS;AAAA,QACjB,SAAS,EAAE,gBAAgB,mBAAmB;AAAA,MAChD,CAAC;AAAA,IACH,QAAQ;AACN,aAAO,IAAI;AAAA,QACT,KAAK,UAAU,EAAE,OAAO,0BAA0B,CAAC;AAAA,QACnD,EAAE,QAAQ,KAAK,SAAS,EAAE,gBAAgB,mBAAmB,EAAE;AAAA,MACjE;AAAA,IACF;AAAA,EACF;AACF;","names":[]}
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":[]}
@@ -66,19 +66,10 @@ function useAdzenPlacement() {
66
66
 
67
67
  // src/copilotkit/react/AdzenCard.tsx
68
68
  var import_jsx_runtime = require("react/jsx-runtime");
69
- var DEFAULT_PROXY_PATH = "/api/adzen/impressions";
70
- function fireBeacon(url, body) {
71
- fetch(url, {
72
- method: "POST",
73
- headers: { "Content-Type": "application/json" },
74
- body
75
- }).catch(() => {
69
+ function fireBeacon(url) {
70
+ fetch(url, { method: "GET", keepalive: true }).catch(() => {
76
71
  setTimeout(() => {
77
- fetch(url, {
78
- method: "POST",
79
- headers: { "Content-Type": "application/json" },
80
- body
81
- }).catch(() => {
72
+ fetch(url, { method: "GET", keepalive: true }).catch(() => {
82
73
  });
83
74
  }, 1e3);
84
75
  });
@@ -87,8 +78,7 @@ function AdzenCard({
87
78
  messageId,
88
79
  adUnitPosition = "chin",
89
80
  viewabilityThresholdMs = 1e3,
90
- className,
91
- impressionProxyPath = DEFAULT_PROXY_PATH
81
+ className
92
82
  }) {
93
83
  const { getAdForMessage } = useAdzenPlacement();
94
84
  const ad = getAdForMessage(messageId);
@@ -96,40 +86,29 @@ function AdzenCard({
96
86
  const [renderFired, setRenderFired] = (0, import_react2.useState)(false);
97
87
  const [viewed, setViewed] = (0, import_react2.useState)(false);
98
88
  (0, import_react2.useEffect)(() => {
99
- if (!ad || renderFired) return;
89
+ if (!ad || renderFired || !ad.render_impression_url) return;
100
90
  setRenderFired(true);
101
- fireBeacon(
102
- impressionProxyPath,
103
- JSON.stringify({
104
- type: "render",
105
- ad_id: ad.ad_id,
106
- message_id: messageId,
107
- ad_unit_position: adUnitPosition,
108
- timestamp: (/* @__PURE__ */ new Date()).toISOString()
109
- })
110
- );
111
- }, [ad, renderFired, messageId, adUnitPosition, impressionProxyPath]);
91
+ const url = new URL(ad.render_impression_url);
92
+ url.searchParams.set("ad_unit_position", adUnitPosition);
93
+ url.searchParams.set("timestamp", (/* @__PURE__ */ new Date()).toISOString());
94
+ fireBeacon(url.toString());
95
+ }, [ad, renderFired, adUnitPosition]);
112
96
  (0, import_react2.useEffect)(() => {
113
- if (!ad || !cardRef.current || viewed) return;
97
+ if (!ad || !cardRef.current || viewed || !ad.view_impression_url) return;
114
98
  return (0, import_core.observeViewability)({
115
99
  element: cardRef.current,
116
100
  thresholdMs: viewabilityThresholdMs,
117
101
  onViewed: () => {
118
102
  setViewed(true);
119
- fireBeacon(
120
- impressionProxyPath,
121
- JSON.stringify({
122
- type: "view",
123
- ad_id: ad.ad_id,
124
- message_id: messageId,
125
- ad_unit_position: adUnitPosition,
126
- viewability_ms: viewabilityThresholdMs,
127
- timestamp: (/* @__PURE__ */ new Date()).toISOString()
128
- })
129
- );
103
+ if (!ad.view_impression_url) return;
104
+ const url = new URL(ad.view_impression_url);
105
+ url.searchParams.set("ad_unit_position", adUnitPosition);
106
+ url.searchParams.set("viewability_ms", String(viewabilityThresholdMs));
107
+ url.searchParams.set("timestamp", (/* @__PURE__ */ new Date()).toISOString());
108
+ fireBeacon(url.toString());
130
109
  }
131
110
  });
132
- }, [ad, viewed, messageId, adUnitPosition, viewabilityThresholdMs, impressionProxyPath]);
111
+ }, [ad, viewed, adUnitPosition, viewabilityThresholdMs]);
133
112
  if (!ad) return null;
134
113
  const initial = ad.advertiser_name?.charAt(0).toUpperCase() ?? "?";
135
114
  return /* @__PURE__ */ (0, import_jsx_runtime.jsxs)(
@@ -1 +1 @@
1
- {"version":3,"sources":["../../../src/copilotkit/react/index.ts","../../../src/copilotkit/react/AdzenCard.tsx","../../../src/copilotkit/react/useAdzenPlacement.ts","../../../src/copilotkit/react/bridge.ts"],"sourcesContent":["export { AdzenCard } from \"./AdzenCard.js\";\nexport type { AdzenCardProps } from \"./AdzenCard.js\";\nexport { useAdzenPlacement } from \"./useAdzenPlacement.js\";\nexport type { UseAdzenPlacementReturn } from \"./useAdzenPlacement.js\";\nexport { dispatchPlacementEvent, dispatchPlacementEvents } from \"./bridge.js\";\n","import { useRef, useEffect, useState } from \"react\";\nimport { observeViewability } from \"@adzenai/core\";\nimport { useAdzenPlacement } from \"./useAdzenPlacement.js\";\n\nexport interface AdzenCardProps {\n messageId: string;\n adUnitPosition?: string;\n viewabilityThresholdMs?: number;\n className?: string;\n /** Relative path on the integrator's server that proxies impression calls. */\n impressionProxyPath?: string;\n}\n\nconst DEFAULT_PROXY_PATH = \"/api/adzen/impressions\";\n\nfunction fireBeacon(url: string, body: string): void {\n fetch(url, {\n method: \"POST\",\n headers: { \"Content-Type\": \"application/json\" },\n body,\n }).catch(() => {\n setTimeout(() => {\n fetch(url, {\n method: \"POST\",\n headers: { \"Content-Type\": \"application/json\" },\n body,\n }).catch(() => {});\n }, 1000);\n });\n}\n\n/**\n * Renders a sponsored ad card for a given assistant message. Returns `null`\n * (no DOM output) when there is no ad for the message.\n */\nexport function AdzenCard({\n messageId,\n adUnitPosition = \"chin\",\n viewabilityThresholdMs = 1000,\n className,\n impressionProxyPath = DEFAULT_PROXY_PATH,\n}: AdzenCardProps) {\n const { getAdForMessage } = useAdzenPlacement();\n const ad = getAdForMessage(messageId);\n const cardRef = useRef<HTMLDivElement>(null);\n const [renderFired, setRenderFired] = useState(false);\n const [viewed, setViewed] = useState(false);\n\n useEffect(() => {\n if (!ad || renderFired) return;\n setRenderFired(true);\n\n fireBeacon(\n impressionProxyPath,\n JSON.stringify({\n type: \"render\",\n ad_id: ad.ad_id,\n message_id: messageId,\n ad_unit_position: adUnitPosition,\n timestamp: new Date().toISOString(),\n }),\n );\n }, [ad, renderFired, messageId, adUnitPosition, impressionProxyPath]);\n\n useEffect(() => {\n if (!ad || !cardRef.current || viewed) return;\n\n return observeViewability({\n element: cardRef.current,\n thresholdMs: viewabilityThresholdMs,\n onViewed: () => {\n setViewed(true);\n fireBeacon(\n impressionProxyPath,\n JSON.stringify({\n type: \"view\",\n ad_id: ad.ad_id,\n message_id: messageId,\n ad_unit_position: adUnitPosition,\n viewability_ms: viewabilityThresholdMs,\n timestamp: new Date().toISOString(),\n }),\n );\n },\n });\n }, [ad, viewed, messageId, adUnitPosition, viewabilityThresholdMs, impressionProxyPath]);\n\n if (!ad) return null;\n\n const initial = ad.advertiser_name?.charAt(0).toUpperCase() ?? \"?\";\n\n return (\n <div\n ref={cardRef}\n className={className}\n data-adzen-ad-unit-position={adUnitPosition}\n style={cardStyles.wrapper}\n >\n <div style={cardStyles.avatar}>\n {ad.advertiser_image_url ? (\n <img\n src={ad.advertiser_image_url}\n alt={ad.advertiser_name}\n style={cardStyles.avatarImg}\n />\n ) : (\n <span style={cardStyles.avatarLetter}>{initial}</span>\n )}\n </div>\n\n <div style={cardStyles.content}>\n <div style={cardStyles.topRow}>\n <span style={cardStyles.advertiserName}>{ad.advertiser_name}</span>\n <span style={cardStyles.relevantAd}>Relevant Ad</span>\n </div>\n <div style={cardStyles.bottomRow}>\n <span style={cardStyles.headline}>{ad.headline}</span>\n <a\n href={ad.destination_url}\n target=\"_blank\"\n rel=\"noopener sponsored\"\n style={cardStyles.cta}\n >\n {ad.cta_text}\n </a>\n </div>\n </div>\n </div>\n );\n}\n\nconst cardStyles: Record<string, React.CSSProperties> = {\n wrapper: {\n display: \"flex\",\n alignItems: \"center\",\n gap: \"0.625rem\",\n fontSize: \"0.85rem\",\n padding: \"0.625rem 0.75rem\",\n },\n avatar: {\n width: 32,\n height: 32,\n borderRadius: \"50%\",\n background: \"#d1d5db\",\n display: \"flex\",\n alignItems: \"center\",\n justifyContent: \"center\",\n flexShrink: 0,\n overflow: \"hidden\",\n },\n avatarImg: {\n width: \"100%\",\n height: \"100%\",\n objectFit: \"cover\" as const,\n borderRadius: \"50%\",\n },\n avatarLetter: {\n fontSize: \"0.8rem\",\n fontWeight: 700,\n color: \"#374151\",\n lineHeight: 1,\n },\n content: {\n display: \"flex\",\n flexDirection: \"column\",\n gap: \"0.125rem\",\n minWidth: 0,\n },\n topRow: {\n display: \"flex\",\n alignItems: \"center\",\n gap: \"0.5rem\",\n },\n advertiserName: {\n fontWeight: 700,\n color: \"inherit\",\n },\n relevantAd: {\n color: \"#8b949e\",\n fontSize: \"0.8rem\",\n },\n bottomRow: {\n display: \"flex\",\n alignItems: \"center\",\n gap: \"0.5rem\",\n whiteSpace: \"nowrap\" as const,\n overflow: \"hidden\",\n textOverflow: \"ellipsis\",\n },\n headline: {\n overflow: \"hidden\",\n textOverflow: \"ellipsis\",\n },\n cta: {\n color: \"#58a6ff\",\n textDecoration: \"none\",\n fontWeight: 500,\n flexShrink: 0,\n },\n};\n","import { useState, useEffect, useCallback } from \"react\";\nimport type { AdzenPlacement } from \"@adzenai/core\";\n\nexport interface UseAdzenPlacementReturn {\n getAdForMessage(messageId: string): AdzenPlacement | null;\n placements: Map<string, AdzenPlacement>;\n}\n\n/**\n * React hook that subscribes to `adzen_placement` custom events and\n * maintains a map of messageId -> AdzenPlacement.\n *\n * Works anywhere in the React tree below `<CopilotKit>` — no wrapper\n * provider required.\n */\nexport function useAdzenPlacement(): UseAdzenPlacementReturn {\n const [placements, setPlacements] = useState<Map<string, AdzenPlacement>>(\n () => new Map(),\n );\n\n useEffect(() => {\n function handler(e: Event) {\n const detail = (e as CustomEvent).detail;\n if (!detail || typeof detail !== \"object\") return;\n\n const placement = detail as AdzenPlacement & { message_id?: string };\n const messageId = placement.message_id;\n if (!messageId) return;\n\n setPlacements((prev) => {\n const existing = prev.get(messageId);\n if (existing && existing.ad_id === placement.ad_id) return prev;\n const next = new Map(prev);\n next.set(messageId, placement);\n return next;\n });\n }\n\n window.addEventListener(\"adzen_placement\", handler);\n return () => window.removeEventListener(\"adzen_placement\", handler);\n }, []);\n\n const getAdForMessage = useCallback(\n (messageId: string): AdzenPlacement | null => {\n return placements.get(messageId) ?? null;\n },\n [placements],\n );\n\n return { getAdForMessage, placements };\n}\n","import type { AdzenPlacement } from \"@adzenai/core\";\n\n/**\n * Checks whether an AG-UI event is an `adzen_placement` custom event\n * and, if so, dispatches it as a browser `CustomEvent` on `window`\n * so that `useAdzenPlacement` can pick it up.\n *\n * Call this for every event returned by `AdzenAsyncMiddleware.processEvent()`.\n * Non-placement events are silently ignored.\n */\nexport function dispatchPlacementEvent(\n event: Record<string, unknown>,\n): boolean {\n if (typeof window === \"undefined\") return false;\n if (event.name !== \"adzen_placement\") return false;\n\n const type = String(event.type ?? \"\");\n if (type !== \"CUSTOM_EVENT\" && type !== \"CUSTOM\" && type !== \"CustomEvent\")\n return false;\n\n const value = event.value as (AdzenPlacement & { message_id?: string }) | undefined;\n if (!value) return false;\n\n window.dispatchEvent(\n new CustomEvent(\"adzen_placement\", { detail: value }),\n );\n return true;\n}\n\n/**\n * Convenience wrapper: iterates an array of AG-UI events (as returned by\n * `AdzenAsyncMiddleware.processEvent()`) and dispatches any placement\n * events to the browser.\n */\nexport function dispatchPlacementEvents(\n events: Record<string, unknown>[],\n): void {\n for (const event of events) {\n dispatchPlacementEvent(event);\n }\n}\n"],"mappings":"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","names":["import_react"]}
1
+ {"version":3,"sources":["../../../src/copilotkit/react/index.ts","../../../src/copilotkit/react/AdzenCard.tsx","../../../src/copilotkit/react/useAdzenPlacement.ts","../../../src/copilotkit/react/bridge.ts"],"sourcesContent":["export { AdzenCard } from \"./AdzenCard.js\";\nexport type { AdzenCardProps } from \"./AdzenCard.js\";\nexport { useAdzenPlacement } from \"./useAdzenPlacement.js\";\nexport type { UseAdzenPlacementReturn } from \"./useAdzenPlacement.js\";\nexport { dispatchPlacementEvent, dispatchPlacementEvents } from \"./bridge.js\";\n","import { useRef, useEffect, useState } from \"react\";\nimport { observeViewability } from \"@adzenai/core\";\nimport { useAdzenPlacement } from \"./useAdzenPlacement.js\";\n\nexport interface AdzenCardProps {\n messageId: string;\n adUnitPosition?: string;\n viewabilityThresholdMs?: number;\n className?: string;\n}\n\nfunction fireBeacon(url: string): void {\n fetch(url, { method: \"GET\", keepalive: true }).catch(() => {\n setTimeout(() => {\n fetch(url, { method: \"GET\", keepalive: true }).catch(() => {});\n }, 1000);\n });\n}\n\n/**\n * Renders a sponsored ad card for a given assistant message. Returns `null`\n * (no DOM output) when there is no ad for the message.\n */\nexport function AdzenCard({\n messageId,\n adUnitPosition = \"chin\",\n viewabilityThresholdMs = 1000,\n className,\n}: AdzenCardProps) {\n const { getAdForMessage } = useAdzenPlacement();\n const ad = getAdForMessage(messageId);\n const cardRef = useRef<HTMLDivElement>(null);\n const [renderFired, setRenderFired] = useState(false);\n const [viewed, setViewed] = useState(false);\n\n useEffect(() => {\n if (!ad || renderFired || !ad.render_impression_url) return;\n setRenderFired(true);\n\n const url = new URL(ad.render_impression_url);\n url.searchParams.set(\"ad_unit_position\", adUnitPosition);\n url.searchParams.set(\"timestamp\", new Date().toISOString());\n fireBeacon(url.toString());\n }, [ad, renderFired, adUnitPosition]);\n\n useEffect(() => {\n if (!ad || !cardRef.current || viewed || !ad.view_impression_url) return;\n\n return observeViewability({\n element: cardRef.current,\n thresholdMs: viewabilityThresholdMs,\n onViewed: () => {\n setViewed(true);\n if (!ad.view_impression_url) return;\n const url = new URL(ad.view_impression_url);\n url.searchParams.set(\"ad_unit_position\", adUnitPosition);\n url.searchParams.set(\"viewability_ms\", String(viewabilityThresholdMs));\n url.searchParams.set(\"timestamp\", new Date().toISOString());\n fireBeacon(url.toString());\n },\n });\n }, [ad, viewed, adUnitPosition, viewabilityThresholdMs]);\n\n if (!ad) return null;\n\n const initial = ad.advertiser_name?.charAt(0).toUpperCase() ?? \"?\";\n\n return (\n <div\n ref={cardRef}\n className={className}\n data-adzen-ad-unit-position={adUnitPosition}\n style={cardStyles.wrapper}\n >\n <div style={cardStyles.avatar}>\n {ad.advertiser_image_url ? (\n <img\n src={ad.advertiser_image_url}\n alt={ad.advertiser_name}\n style={cardStyles.avatarImg}\n />\n ) : (\n <span style={cardStyles.avatarLetter}>{initial}</span>\n )}\n </div>\n\n <div style={cardStyles.content}>\n <div style={cardStyles.topRow}>\n <span style={cardStyles.advertiserName}>{ad.advertiser_name}</span>\n <span style={cardStyles.relevantAd}>Relevant Ad</span>\n </div>\n <div style={cardStyles.bottomRow}>\n <span style={cardStyles.headline}>{ad.headline}</span>\n <a\n href={ad.destination_url}\n target=\"_blank\"\n rel=\"noopener sponsored\"\n style={cardStyles.cta}\n >\n {ad.cta_text}\n </a>\n </div>\n </div>\n </div>\n );\n}\n\nconst cardStyles: Record<string, React.CSSProperties> = {\n wrapper: {\n display: \"flex\",\n alignItems: \"center\",\n gap: \"0.625rem\",\n fontSize: \"0.85rem\",\n padding: \"0.625rem 0.75rem\",\n },\n avatar: {\n width: 32,\n height: 32,\n borderRadius: \"50%\",\n background: \"#d1d5db\",\n display: \"flex\",\n alignItems: \"center\",\n justifyContent: \"center\",\n flexShrink: 0,\n overflow: \"hidden\",\n },\n avatarImg: {\n width: \"100%\",\n height: \"100%\",\n objectFit: \"cover\" as const,\n borderRadius: \"50%\",\n },\n avatarLetter: {\n fontSize: \"0.8rem\",\n fontWeight: 700,\n color: \"#374151\",\n lineHeight: 1,\n },\n content: {\n display: \"flex\",\n flexDirection: \"column\",\n gap: \"0.125rem\",\n minWidth: 0,\n },\n topRow: {\n display: \"flex\",\n alignItems: \"center\",\n gap: \"0.5rem\",\n },\n advertiserName: {\n fontWeight: 700,\n color: \"inherit\",\n },\n relevantAd: {\n color: \"#8b949e\",\n fontSize: \"0.8rem\",\n },\n bottomRow: {\n display: \"flex\",\n alignItems: \"center\",\n gap: \"0.5rem\",\n whiteSpace: \"nowrap\" as const,\n overflow: \"hidden\",\n textOverflow: \"ellipsis\",\n },\n headline: {\n overflow: \"hidden\",\n textOverflow: \"ellipsis\",\n },\n cta: {\n color: \"#58a6ff\",\n textDecoration: \"none\",\n fontWeight: 500,\n flexShrink: 0,\n },\n};\n","import { useState, useEffect, useCallback } from \"react\";\nimport type { AdzenPlacement } from \"@adzenai/core\";\n\nexport interface UseAdzenPlacementReturn {\n getAdForMessage(messageId: string): AdzenPlacement | null;\n placements: Map<string, AdzenPlacement>;\n}\n\n/**\n * React hook that subscribes to `adzen_placement` custom events and\n * maintains a map of messageId -> AdzenPlacement.\n *\n * Works anywhere in the React tree below `<CopilotKit>` — no wrapper\n * provider required.\n */\nexport function useAdzenPlacement(): UseAdzenPlacementReturn {\n const [placements, setPlacements] = useState<Map<string, AdzenPlacement>>(\n () => new Map(),\n );\n\n useEffect(() => {\n function handler(e: Event) {\n const detail = (e as CustomEvent).detail;\n if (!detail || typeof detail !== \"object\") return;\n\n const placement = detail as AdzenPlacement & { message_id?: string };\n const messageId = placement.message_id;\n if (!messageId) return;\n\n setPlacements((prev) => {\n const existing = prev.get(messageId);\n if (existing && existing.ad_id === placement.ad_id) return prev;\n const next = new Map(prev);\n next.set(messageId, placement);\n return next;\n });\n }\n\n window.addEventListener(\"adzen_placement\", handler);\n return () => window.removeEventListener(\"adzen_placement\", handler);\n }, []);\n\n const getAdForMessage = useCallback(\n (messageId: string): AdzenPlacement | null => {\n return placements.get(messageId) ?? null;\n },\n [placements],\n );\n\n return { getAdForMessage, placements };\n}\n","import type { AdzenPlacement } from \"@adzenai/core\";\n\n/**\n * Checks whether an AG-UI event is an `adzen_placement` custom event\n * and, if so, dispatches it as a browser `CustomEvent` on `window`\n * so that `useAdzenPlacement` can pick it up.\n *\n * Call this for every event returned by `AdzenAsyncMiddleware.processEvent()`.\n * Non-placement events are silently ignored.\n */\nexport function dispatchPlacementEvent(\n event: Record<string, unknown>,\n): boolean {\n if (typeof window === \"undefined\") return false;\n if (event.name !== \"adzen_placement\") return false;\n\n const type = String(event.type ?? \"\");\n if (type !== \"CUSTOM_EVENT\" && type !== \"CUSTOM\" && type !== \"CustomEvent\")\n return false;\n\n const value = event.value as (AdzenPlacement & { message_id?: string }) | undefined;\n if (!value) return false;\n\n window.dispatchEvent(\n new CustomEvent(\"adzen_placement\", { detail: value }),\n );\n return true;\n}\n\n/**\n * Convenience wrapper: iterates an array of AG-UI events (as returned by\n * `AdzenAsyncMiddleware.processEvent()`) and dispatches any placement\n * events to the browser.\n */\nexport function dispatchPlacementEvents(\n events: Record<string, unknown>[],\n): void {\n for (const event of events) {\n dispatchPlacementEvent(event);\n }\n}\n"],"mappings":"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","names":["import_react"]}
@@ -6,14 +6,12 @@ interface AdzenCardProps {
6
6
  adUnitPosition?: string;
7
7
  viewabilityThresholdMs?: number;
8
8
  className?: string;
9
- /** Relative path on the integrator's server that proxies impression calls. */
10
- impressionProxyPath?: string;
11
9
  }
12
10
  /**
13
11
  * Renders a sponsored ad card for a given assistant message. Returns `null`
14
12
  * (no DOM output) when there is no ad for the message.
15
13
  */
16
- declare function AdzenCard({ messageId, adUnitPosition, viewabilityThresholdMs, className, impressionProxyPath, }: AdzenCardProps): react.JSX.Element | null;
14
+ declare function AdzenCard({ messageId, adUnitPosition, viewabilityThresholdMs, className, }: AdzenCardProps): react.JSX.Element | null;
17
15
 
18
16
  interface UseAdzenPlacementReturn {
19
17
  getAdForMessage(messageId: string): AdzenPlacement | null;
@@ -6,14 +6,12 @@ interface AdzenCardProps {
6
6
  adUnitPosition?: string;
7
7
  viewabilityThresholdMs?: number;
8
8
  className?: string;
9
- /** Relative path on the integrator's server that proxies impression calls. */
10
- impressionProxyPath?: string;
11
9
  }
12
10
  /**
13
11
  * Renders a sponsored ad card for a given assistant message. Returns `null`
14
12
  * (no DOM output) when there is no ad for the message.
15
13
  */
16
- declare function AdzenCard({ messageId, adUnitPosition, viewabilityThresholdMs, className, impressionProxyPath, }: AdzenCardProps): react.JSX.Element | null;
14
+ declare function AdzenCard({ messageId, adUnitPosition, viewabilityThresholdMs, className, }: AdzenCardProps): react.JSX.Element | null;
17
15
 
18
16
  interface UseAdzenPlacementReturn {
19
17
  getAdForMessage(messageId: string): AdzenPlacement | null;
@@ -37,19 +37,10 @@ function useAdzenPlacement() {
37
37
 
38
38
  // src/copilotkit/react/AdzenCard.tsx
39
39
  import { jsx, jsxs } from "react/jsx-runtime";
40
- var DEFAULT_PROXY_PATH = "/api/adzen/impressions";
41
- function fireBeacon(url, body) {
42
- fetch(url, {
43
- method: "POST",
44
- headers: { "Content-Type": "application/json" },
45
- body
46
- }).catch(() => {
40
+ function fireBeacon(url) {
41
+ fetch(url, { method: "GET", keepalive: true }).catch(() => {
47
42
  setTimeout(() => {
48
- fetch(url, {
49
- method: "POST",
50
- headers: { "Content-Type": "application/json" },
51
- body
52
- }).catch(() => {
43
+ fetch(url, { method: "GET", keepalive: true }).catch(() => {
53
44
  });
54
45
  }, 1e3);
55
46
  });
@@ -58,8 +49,7 @@ function AdzenCard({
58
49
  messageId,
59
50
  adUnitPosition = "chin",
60
51
  viewabilityThresholdMs = 1e3,
61
- className,
62
- impressionProxyPath = DEFAULT_PROXY_PATH
52
+ className
63
53
  }) {
64
54
  const { getAdForMessage } = useAdzenPlacement();
65
55
  const ad = getAdForMessage(messageId);
@@ -67,40 +57,29 @@ function AdzenCard({
67
57
  const [renderFired, setRenderFired] = useState2(false);
68
58
  const [viewed, setViewed] = useState2(false);
69
59
  useEffect2(() => {
70
- if (!ad || renderFired) return;
60
+ if (!ad || renderFired || !ad.render_impression_url) return;
71
61
  setRenderFired(true);
72
- fireBeacon(
73
- impressionProxyPath,
74
- JSON.stringify({
75
- type: "render",
76
- ad_id: ad.ad_id,
77
- message_id: messageId,
78
- ad_unit_position: adUnitPosition,
79
- timestamp: (/* @__PURE__ */ new Date()).toISOString()
80
- })
81
- );
82
- }, [ad, renderFired, messageId, adUnitPosition, impressionProxyPath]);
62
+ const url = new URL(ad.render_impression_url);
63
+ url.searchParams.set("ad_unit_position", adUnitPosition);
64
+ url.searchParams.set("timestamp", (/* @__PURE__ */ new Date()).toISOString());
65
+ fireBeacon(url.toString());
66
+ }, [ad, renderFired, adUnitPosition]);
83
67
  useEffect2(() => {
84
- if (!ad || !cardRef.current || viewed) return;
68
+ if (!ad || !cardRef.current || viewed || !ad.view_impression_url) return;
85
69
  return observeViewability({
86
70
  element: cardRef.current,
87
71
  thresholdMs: viewabilityThresholdMs,
88
72
  onViewed: () => {
89
73
  setViewed(true);
90
- fireBeacon(
91
- impressionProxyPath,
92
- JSON.stringify({
93
- type: "view",
94
- ad_id: ad.ad_id,
95
- message_id: messageId,
96
- ad_unit_position: adUnitPosition,
97
- viewability_ms: viewabilityThresholdMs,
98
- timestamp: (/* @__PURE__ */ new Date()).toISOString()
99
- })
100
- );
74
+ if (!ad.view_impression_url) return;
75
+ const url = new URL(ad.view_impression_url);
76
+ url.searchParams.set("ad_unit_position", adUnitPosition);
77
+ url.searchParams.set("viewability_ms", String(viewabilityThresholdMs));
78
+ url.searchParams.set("timestamp", (/* @__PURE__ */ new Date()).toISOString());
79
+ fireBeacon(url.toString());
101
80
  }
102
81
  });
103
- }, [ad, viewed, messageId, adUnitPosition, viewabilityThresholdMs, impressionProxyPath]);
82
+ }, [ad, viewed, adUnitPosition, viewabilityThresholdMs]);
104
83
  if (!ad) return null;
105
84
  const initial = ad.advertiser_name?.charAt(0).toUpperCase() ?? "?";
106
85
  return /* @__PURE__ */ jsxs(
@@ -1 +1 @@
1
- {"version":3,"sources":["../../../src/copilotkit/react/AdzenCard.tsx","../../../src/copilotkit/react/useAdzenPlacement.ts","../../../src/copilotkit/react/bridge.ts"],"sourcesContent":["import { useRef, useEffect, useState } from \"react\";\nimport { observeViewability } from \"@adzenai/core\";\nimport { useAdzenPlacement } from \"./useAdzenPlacement.js\";\n\nexport interface AdzenCardProps {\n messageId: string;\n adUnitPosition?: string;\n viewabilityThresholdMs?: number;\n className?: string;\n /** Relative path on the integrator's server that proxies impression calls. */\n impressionProxyPath?: string;\n}\n\nconst DEFAULT_PROXY_PATH = \"/api/adzen/impressions\";\n\nfunction fireBeacon(url: string, body: string): void {\n fetch(url, {\n method: \"POST\",\n headers: { \"Content-Type\": \"application/json\" },\n body,\n }).catch(() => {\n setTimeout(() => {\n fetch(url, {\n method: \"POST\",\n headers: { \"Content-Type\": \"application/json\" },\n body,\n }).catch(() => {});\n }, 1000);\n });\n}\n\n/**\n * Renders a sponsored ad card for a given assistant message. Returns `null`\n * (no DOM output) when there is no ad for the message.\n */\nexport function AdzenCard({\n messageId,\n adUnitPosition = \"chin\",\n viewabilityThresholdMs = 1000,\n className,\n impressionProxyPath = DEFAULT_PROXY_PATH,\n}: AdzenCardProps) {\n const { getAdForMessage } = useAdzenPlacement();\n const ad = getAdForMessage(messageId);\n const cardRef = useRef<HTMLDivElement>(null);\n const [renderFired, setRenderFired] = useState(false);\n const [viewed, setViewed] = useState(false);\n\n useEffect(() => {\n if (!ad || renderFired) return;\n setRenderFired(true);\n\n fireBeacon(\n impressionProxyPath,\n JSON.stringify({\n type: \"render\",\n ad_id: ad.ad_id,\n message_id: messageId,\n ad_unit_position: adUnitPosition,\n timestamp: new Date().toISOString(),\n }),\n );\n }, [ad, renderFired, messageId, adUnitPosition, impressionProxyPath]);\n\n useEffect(() => {\n if (!ad || !cardRef.current || viewed) return;\n\n return observeViewability({\n element: cardRef.current,\n thresholdMs: viewabilityThresholdMs,\n onViewed: () => {\n setViewed(true);\n fireBeacon(\n impressionProxyPath,\n JSON.stringify({\n type: \"view\",\n ad_id: ad.ad_id,\n message_id: messageId,\n ad_unit_position: adUnitPosition,\n viewability_ms: viewabilityThresholdMs,\n timestamp: new Date().toISOString(),\n }),\n );\n },\n });\n }, [ad, viewed, messageId, adUnitPosition, viewabilityThresholdMs, impressionProxyPath]);\n\n if (!ad) return null;\n\n const initial = ad.advertiser_name?.charAt(0).toUpperCase() ?? \"?\";\n\n return (\n <div\n ref={cardRef}\n className={className}\n data-adzen-ad-unit-position={adUnitPosition}\n style={cardStyles.wrapper}\n >\n <div style={cardStyles.avatar}>\n {ad.advertiser_image_url ? (\n <img\n src={ad.advertiser_image_url}\n alt={ad.advertiser_name}\n style={cardStyles.avatarImg}\n />\n ) : (\n <span style={cardStyles.avatarLetter}>{initial}</span>\n )}\n </div>\n\n <div style={cardStyles.content}>\n <div style={cardStyles.topRow}>\n <span style={cardStyles.advertiserName}>{ad.advertiser_name}</span>\n <span style={cardStyles.relevantAd}>Relevant Ad</span>\n </div>\n <div style={cardStyles.bottomRow}>\n <span style={cardStyles.headline}>{ad.headline}</span>\n <a\n href={ad.destination_url}\n target=\"_blank\"\n rel=\"noopener sponsored\"\n style={cardStyles.cta}\n >\n {ad.cta_text}\n </a>\n </div>\n </div>\n </div>\n );\n}\n\nconst cardStyles: Record<string, React.CSSProperties> = {\n wrapper: {\n display: \"flex\",\n alignItems: \"center\",\n gap: \"0.625rem\",\n fontSize: \"0.85rem\",\n padding: \"0.625rem 0.75rem\",\n },\n avatar: {\n width: 32,\n height: 32,\n borderRadius: \"50%\",\n background: \"#d1d5db\",\n display: \"flex\",\n alignItems: \"center\",\n justifyContent: \"center\",\n flexShrink: 0,\n overflow: \"hidden\",\n },\n avatarImg: {\n width: \"100%\",\n height: \"100%\",\n objectFit: \"cover\" as const,\n borderRadius: \"50%\",\n },\n avatarLetter: {\n fontSize: \"0.8rem\",\n fontWeight: 700,\n color: \"#374151\",\n lineHeight: 1,\n },\n content: {\n display: \"flex\",\n flexDirection: \"column\",\n gap: \"0.125rem\",\n minWidth: 0,\n },\n topRow: {\n display: \"flex\",\n alignItems: \"center\",\n gap: \"0.5rem\",\n },\n advertiserName: {\n fontWeight: 700,\n color: \"inherit\",\n },\n relevantAd: {\n color: \"#8b949e\",\n fontSize: \"0.8rem\",\n },\n bottomRow: {\n display: \"flex\",\n alignItems: \"center\",\n gap: \"0.5rem\",\n whiteSpace: \"nowrap\" as const,\n overflow: \"hidden\",\n textOverflow: \"ellipsis\",\n },\n headline: {\n overflow: \"hidden\",\n textOverflow: \"ellipsis\",\n },\n cta: {\n color: \"#58a6ff\",\n textDecoration: \"none\",\n fontWeight: 500,\n flexShrink: 0,\n },\n};\n","import { useState, useEffect, useCallback } from \"react\";\nimport type { AdzenPlacement } from \"@adzenai/core\";\n\nexport interface UseAdzenPlacementReturn {\n getAdForMessage(messageId: string): AdzenPlacement | null;\n placements: Map<string, AdzenPlacement>;\n}\n\n/**\n * React hook that subscribes to `adzen_placement` custom events and\n * maintains a map of messageId -> AdzenPlacement.\n *\n * Works anywhere in the React tree below `<CopilotKit>` — no wrapper\n * provider required.\n */\nexport function useAdzenPlacement(): UseAdzenPlacementReturn {\n const [placements, setPlacements] = useState<Map<string, AdzenPlacement>>(\n () => new Map(),\n );\n\n useEffect(() => {\n function handler(e: Event) {\n const detail = (e as CustomEvent).detail;\n if (!detail || typeof detail !== \"object\") return;\n\n const placement = detail as AdzenPlacement & { message_id?: string };\n const messageId = placement.message_id;\n if (!messageId) return;\n\n setPlacements((prev) => {\n const existing = prev.get(messageId);\n if (existing && existing.ad_id === placement.ad_id) return prev;\n const next = new Map(prev);\n next.set(messageId, placement);\n return next;\n });\n }\n\n window.addEventListener(\"adzen_placement\", handler);\n return () => window.removeEventListener(\"adzen_placement\", handler);\n }, []);\n\n const getAdForMessage = useCallback(\n (messageId: string): AdzenPlacement | null => {\n return placements.get(messageId) ?? null;\n },\n [placements],\n );\n\n return { getAdForMessage, placements };\n}\n","import type { AdzenPlacement } from \"@adzenai/core\";\n\n/**\n * Checks whether an AG-UI event is an `adzen_placement` custom event\n * and, if so, dispatches it as a browser `CustomEvent` on `window`\n * so that `useAdzenPlacement` can pick it up.\n *\n * Call this for every event returned by `AdzenAsyncMiddleware.processEvent()`.\n * Non-placement events are silently ignored.\n */\nexport function dispatchPlacementEvent(\n event: Record<string, unknown>,\n): boolean {\n if (typeof window === \"undefined\") return false;\n if (event.name !== \"adzen_placement\") return false;\n\n const type = String(event.type ?? \"\");\n if (type !== \"CUSTOM_EVENT\" && type !== \"CUSTOM\" && type !== \"CustomEvent\")\n return false;\n\n const value = event.value as (AdzenPlacement & { message_id?: string }) | undefined;\n if (!value) return false;\n\n window.dispatchEvent(\n new CustomEvent(\"adzen_placement\", { detail: value }),\n );\n return true;\n}\n\n/**\n * Convenience wrapper: iterates an array of AG-UI events (as returned by\n * `AdzenAsyncMiddleware.processEvent()`) and dispatches any placement\n * events to the browser.\n */\nexport function dispatchPlacementEvents(\n events: Record<string, unknown>[],\n): void {\n for (const event of events) {\n dispatchPlacementEvent(event);\n }\n}\n"],"mappings":"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","names":["useEffect","useState","useState","useEffect"]}
1
+ {"version":3,"sources":["../../../src/copilotkit/react/AdzenCard.tsx","../../../src/copilotkit/react/useAdzenPlacement.ts","../../../src/copilotkit/react/bridge.ts"],"sourcesContent":["import { useRef, useEffect, useState } from \"react\";\nimport { observeViewability } from \"@adzenai/core\";\nimport { useAdzenPlacement } from \"./useAdzenPlacement.js\";\n\nexport interface AdzenCardProps {\n messageId: string;\n adUnitPosition?: string;\n viewabilityThresholdMs?: number;\n className?: string;\n}\n\nfunction fireBeacon(url: string): void {\n fetch(url, { method: \"GET\", keepalive: true }).catch(() => {\n setTimeout(() => {\n fetch(url, { method: \"GET\", keepalive: true }).catch(() => {});\n }, 1000);\n });\n}\n\n/**\n * Renders a sponsored ad card for a given assistant message. Returns `null`\n * (no DOM output) when there is no ad for the message.\n */\nexport function AdzenCard({\n messageId,\n adUnitPosition = \"chin\",\n viewabilityThresholdMs = 1000,\n className,\n}: AdzenCardProps) {\n const { getAdForMessage } = useAdzenPlacement();\n const ad = getAdForMessage(messageId);\n const cardRef = useRef<HTMLDivElement>(null);\n const [renderFired, setRenderFired] = useState(false);\n const [viewed, setViewed] = useState(false);\n\n useEffect(() => {\n if (!ad || renderFired || !ad.render_impression_url) return;\n setRenderFired(true);\n\n const url = new URL(ad.render_impression_url);\n url.searchParams.set(\"ad_unit_position\", adUnitPosition);\n url.searchParams.set(\"timestamp\", new Date().toISOString());\n fireBeacon(url.toString());\n }, [ad, renderFired, adUnitPosition]);\n\n useEffect(() => {\n if (!ad || !cardRef.current || viewed || !ad.view_impression_url) return;\n\n return observeViewability({\n element: cardRef.current,\n thresholdMs: viewabilityThresholdMs,\n onViewed: () => {\n setViewed(true);\n if (!ad.view_impression_url) return;\n const url = new URL(ad.view_impression_url);\n url.searchParams.set(\"ad_unit_position\", adUnitPosition);\n url.searchParams.set(\"viewability_ms\", String(viewabilityThresholdMs));\n url.searchParams.set(\"timestamp\", new Date().toISOString());\n fireBeacon(url.toString());\n },\n });\n }, [ad, viewed, adUnitPosition, viewabilityThresholdMs]);\n\n if (!ad) return null;\n\n const initial = ad.advertiser_name?.charAt(0).toUpperCase() ?? \"?\";\n\n return (\n <div\n ref={cardRef}\n className={className}\n data-adzen-ad-unit-position={adUnitPosition}\n style={cardStyles.wrapper}\n >\n <div style={cardStyles.avatar}>\n {ad.advertiser_image_url ? (\n <img\n src={ad.advertiser_image_url}\n alt={ad.advertiser_name}\n style={cardStyles.avatarImg}\n />\n ) : (\n <span style={cardStyles.avatarLetter}>{initial}</span>\n )}\n </div>\n\n <div style={cardStyles.content}>\n <div style={cardStyles.topRow}>\n <span style={cardStyles.advertiserName}>{ad.advertiser_name}</span>\n <span style={cardStyles.relevantAd}>Relevant Ad</span>\n </div>\n <div style={cardStyles.bottomRow}>\n <span style={cardStyles.headline}>{ad.headline}</span>\n <a\n href={ad.destination_url}\n target=\"_blank\"\n rel=\"noopener sponsored\"\n style={cardStyles.cta}\n >\n {ad.cta_text}\n </a>\n </div>\n </div>\n </div>\n );\n}\n\nconst cardStyles: Record<string, React.CSSProperties> = {\n wrapper: {\n display: \"flex\",\n alignItems: \"center\",\n gap: \"0.625rem\",\n fontSize: \"0.85rem\",\n padding: \"0.625rem 0.75rem\",\n },\n avatar: {\n width: 32,\n height: 32,\n borderRadius: \"50%\",\n background: \"#d1d5db\",\n display: \"flex\",\n alignItems: \"center\",\n justifyContent: \"center\",\n flexShrink: 0,\n overflow: \"hidden\",\n },\n avatarImg: {\n width: \"100%\",\n height: \"100%\",\n objectFit: \"cover\" as const,\n borderRadius: \"50%\",\n },\n avatarLetter: {\n fontSize: \"0.8rem\",\n fontWeight: 700,\n color: \"#374151\",\n lineHeight: 1,\n },\n content: {\n display: \"flex\",\n flexDirection: \"column\",\n gap: \"0.125rem\",\n minWidth: 0,\n },\n topRow: {\n display: \"flex\",\n alignItems: \"center\",\n gap: \"0.5rem\",\n },\n advertiserName: {\n fontWeight: 700,\n color: \"inherit\",\n },\n relevantAd: {\n color: \"#8b949e\",\n fontSize: \"0.8rem\",\n },\n bottomRow: {\n display: \"flex\",\n alignItems: \"center\",\n gap: \"0.5rem\",\n whiteSpace: \"nowrap\" as const,\n overflow: \"hidden\",\n textOverflow: \"ellipsis\",\n },\n headline: {\n overflow: \"hidden\",\n textOverflow: \"ellipsis\",\n },\n cta: {\n color: \"#58a6ff\",\n textDecoration: \"none\",\n fontWeight: 500,\n flexShrink: 0,\n },\n};\n","import { useState, useEffect, useCallback } from \"react\";\nimport type { AdzenPlacement } from \"@adzenai/core\";\n\nexport interface UseAdzenPlacementReturn {\n getAdForMessage(messageId: string): AdzenPlacement | null;\n placements: Map<string, AdzenPlacement>;\n}\n\n/**\n * React hook that subscribes to `adzen_placement` custom events and\n * maintains a map of messageId -> AdzenPlacement.\n *\n * Works anywhere in the React tree below `<CopilotKit>` — no wrapper\n * provider required.\n */\nexport function useAdzenPlacement(): UseAdzenPlacementReturn {\n const [placements, setPlacements] = useState<Map<string, AdzenPlacement>>(\n () => new Map(),\n );\n\n useEffect(() => {\n function handler(e: Event) {\n const detail = (e as CustomEvent).detail;\n if (!detail || typeof detail !== \"object\") return;\n\n const placement = detail as AdzenPlacement & { message_id?: string };\n const messageId = placement.message_id;\n if (!messageId) return;\n\n setPlacements((prev) => {\n const existing = prev.get(messageId);\n if (existing && existing.ad_id === placement.ad_id) return prev;\n const next = new Map(prev);\n next.set(messageId, placement);\n return next;\n });\n }\n\n window.addEventListener(\"adzen_placement\", handler);\n return () => window.removeEventListener(\"adzen_placement\", handler);\n }, []);\n\n const getAdForMessage = useCallback(\n (messageId: string): AdzenPlacement | null => {\n return placements.get(messageId) ?? null;\n },\n [placements],\n );\n\n return { getAdForMessage, placements };\n}\n","import type { AdzenPlacement } from \"@adzenai/core\";\n\n/**\n * Checks whether an AG-UI event is an `adzen_placement` custom event\n * and, if so, dispatches it as a browser `CustomEvent` on `window`\n * so that `useAdzenPlacement` can pick it up.\n *\n * Call this for every event returned by `AdzenAsyncMiddleware.processEvent()`.\n * Non-placement events are silently ignored.\n */\nexport function dispatchPlacementEvent(\n event: Record<string, unknown>,\n): boolean {\n if (typeof window === \"undefined\") return false;\n if (event.name !== \"adzen_placement\") return false;\n\n const type = String(event.type ?? \"\");\n if (type !== \"CUSTOM_EVENT\" && type !== \"CUSTOM\" && type !== \"CustomEvent\")\n return false;\n\n const value = event.value as (AdzenPlacement & { message_id?: string }) | undefined;\n if (!value) return false;\n\n window.dispatchEvent(\n new CustomEvent(\"adzen_placement\", { detail: value }),\n );\n return true;\n}\n\n/**\n * Convenience wrapper: iterates an array of AG-UI events (as returned by\n * `AdzenAsyncMiddleware.processEvent()`) and dispatches any placement\n * events to the browser.\n */\nexport function dispatchPlacementEvents(\n events: Record<string, unknown>[],\n): void {\n for (const event of events) {\n dispatchPlacementEvent(event);\n }\n}\n"],"mappings":"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","names":["useEffect","useState","useState","useEffect"]}
@@ -1 +1 @@
1
- {"version":3,"sources":["../src/index.ts"],"sourcesContent":["export type {\n AdzenPlacement,\n AdzenDecision,\n TrackingInfo,\n AdzenBaseConfig,\n} from \"@adzenai/core\";\n"],"mappings":";;;;;;;;;;;;;;;;AAAA;AAAA;","names":[]}
1
+ {"version":3,"sources":["../src/index.ts"],"sourcesContent":["export type {\n AdzenPlacement,\n ProcessResponse,\n ProcessResponseAd,\n AdzenBaseConfig,\n} from \"@adzenai/core\";\n"],"mappings":";;;;;;;;;;;;;;;;AAAA;AAAA;","names":[]}
package/dist/index.d.cts CHANGED
@@ -1 +1 @@
1
- export { AdzenBaseConfig, AdzenDecision, AdzenPlacement, TrackingInfo } from '@adzenai/core';
1
+ export { AdzenBaseConfig, AdzenPlacement, ProcessResponse, ProcessResponseAd } from '@adzenai/core';
package/dist/index.d.ts CHANGED
@@ -1 +1 @@
1
- export { AdzenBaseConfig, AdzenDecision, AdzenPlacement, TrackingInfo } from '@adzenai/core';
1
+ export { AdzenBaseConfig, AdzenPlacement, ProcessResponse, ProcessResponseAd } from '@adzenai/core';
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@adzenai/ai",
3
- "version": "1.0.0",
3
+ "version": "1.1.1",
4
4
  "description": "AI-powered ad integration for CopilotKit and AG-UI agents",
5
5
  "license": "MIT",
6
6
  "type": "module",
@@ -45,15 +45,8 @@
45
45
  "files": [
46
46
  "dist"
47
47
  ],
48
- "scripts": {
49
- "build": "tsup",
50
- "test": "vitest run",
51
- "clean": "rm -rf dist",
52
- "typecheck": "tsc --noEmit",
53
- "demo": "vite --config src/copilotkit/demo/vite.config.ts"
54
- },
55
48
  "dependencies": {
56
- "@adzenai/core": "workspace:*"
49
+ "@adzenai/core": "1.1.1"
57
50
  },
58
51
  "peerDependencies": {
59
52
  "@ag-ui/client": "*",
@@ -86,5 +79,12 @@
86
79
  "typescript": "^5.5.0",
87
80
  "vite": "^6.0.0",
88
81
  "vitest": "^2.1.0"
82
+ },
83
+ "scripts": {
84
+ "build": "tsup",
85
+ "test": "vitest run",
86
+ "clean": "rm -rf dist",
87
+ "typecheck": "tsc --noEmit",
88
+ "demo": "vite --config src/copilotkit/demo/vite.config.ts"
89
89
  }
90
- }
90
+ }