@adzenai/ai 1.1.0 → 1.2.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/README.md CHANGED
@@ -51,8 +51,8 @@ dispatchPlacementEvents(events);
51
51
  | Path | Environment | Provides |
52
52
  | --- | --- | --- |
53
53
  | `@adzenai/ai` | Any | Core type re-exports (`AdzenPlacement`, `ProcessResponse`, `ProcessResponseAd`) |
54
- | `@adzenai/ai/copilotkit` | Server or client | `AdzenAsyncMiddleware`, `AdzenAsyncConfig` |
55
- | `@adzenai/ai/copilotkit/react` | Browser (React 18+) | `AdzenCard`, `useAdzenPlacement`, `dispatchPlacementEvent(s)` |
54
+ | `@adzenai/ai/copilotkit` | Async: server or client. Stream: server only. | `AdzenAsyncMiddleware`, `AdzenAsyncConfig`, `AdzenStreamMiddleware`, `AdzenStreamConfig` |
55
+ | `@adzenai/ai/copilotkit/react` | Browser (React 18+) | `AdzenCard`, `InlineAd`, `useAdzenPlacement`, `useAdzenInlineAds`, `buildInlineAdSegments`, `dispatchPlacementEvent(s)` |
56
56
 
57
57
  ## How It Works
58
58
 
@@ -66,19 +66,67 @@ Impression beacons are fired as client-side GET requests directly to the deliver
66
66
 
67
67
  The middleware is **additive only** — it never modifies, blocks, or delays the original event stream. All failures degrade silently.
68
68
 
69
+ ## Streaming Enrichment
70
+
71
+ `AdzenStreamMiddleware` proxies the LLM response stream through Adzen's `/stream` endpoint for in-message ads, instead of waiting for the completed message. Tee the upstream response so one branch feeds AG-UI parsing and the other is piped to Adzen:
72
+
73
+ ```typescript
74
+ const [parseBranch, proxyBranch] = res.body!.tee();
75
+
76
+ for await (const evt of adzen.processStream(parseIntoAgUiEvents(parseBranch), {
77
+ prompt,
78
+ upstreamBody: proxyBranch,
79
+ })) {
80
+ sendToClient(evt);
81
+ }
82
+ ```
83
+
84
+ **Server-side only (Node 18+).** It passes a `ReadableStream` as the fetch request body; browsers other than Chromium do not support this and coerce the stream to the string `"[object ReadableStream]"`. The SDK feature-detects and emits an `adzen_error` rather than sending a corrupted body.
85
+
86
+ ### Rendering inline ads
87
+
88
+ Inline ads carry a `content_offset` — the number of message characters streamed before the ad arrived — so they render at that point in the text rather than at the end of the message. `getInlineAdSegments()` does the splitting:
89
+
90
+ ```tsx
91
+ const { getInlineAdSegments } = useAdzenInlineAds();
92
+
93
+ <div style={{ whiteSpace: "pre-wrap" }}>
94
+ {getInlineAdSegments(msg.id, msg.content).map((segment, i) =>
95
+ segment.kind === "text" ? (
96
+ <span key={i}>{segment.text}</span>
97
+ ) : (
98
+ <InlineAd key={segment.ad.ad_id} ad={segment.ad} />
99
+ ),
100
+ )}
101
+ </div>
102
+ ```
103
+
104
+ `InlineAd` renders as a plain text snippet — an "Ad" pill, the advertiser name, and the CTA as the only link (`[AD] Brooks Brothers clothing sale`) — and is block-level, so it appears on its own line directly below the text it follows. The advertiser text uses `creative.advertiser_name`, falling back to `creative.headline`.
105
+
106
+ Anchors are snapped forward to the next paragraph break, so segments are always whole paragraphs and each one can be passed to a Markdown renderer (`<Markdown>{segment.text}</Markdown>`) without a split breaking `**bold text**`, a list, or a code fence.
107
+
108
+ At most one ad is placed per paragraph break. Ads that would land on an already-used break move to the next free one (the end of the message counts as a slot), and any that still have nowhere to go are held back until the message grows, rather than stacking in one spot.
109
+
69
110
  ## Configuration
70
111
 
71
112
  ```typescript
72
113
  new AdzenAsyncMiddleware({
73
114
  apiKey: string; // required — Adzen API key
74
- endpointUrl?: string; // default: "https://api.adzen.ai/v1/ai"
115
+ endpointUrl?: string; // default: "https://api.adzen.ai/v1"
75
116
  timeoutMs?: number; // default: 3000
76
117
  adUnitPosition?: string; // default: "chin"
77
118
  location?: string; // optional — DMA location code for geo-targeting
78
119
  conversationId?: string; // optional — links impressions to conversation context
120
+ profileId?: string; // optional — sent as X-Profile-Id where APIM does not inject it
79
121
  });
80
122
  ```
81
123
 
124
+ `endpointUrl` is a base URL; the SDK appends `/process` and `/stream`. Include any environment-specific path prefix (such as `/ai` on stage and prod) in the value you pass.
125
+
126
+ `AdzenStreamMiddleware` accepts the same options plus `prefetch` (POST `/process` with the prompt before opening the stream), `placementPollIntervalMs`, and the `onInlineAd` / `onPlacement` / `onError` / `onStreamStart` / `onStreamEnd` callbacks.
127
+
128
+ The middleware never makes the assistant message wait on the ad pipeline: the prefetch is dispatched without being awaited, and content events are forwarded as they arrive whether or not an ad has been matched yet.
129
+
82
130
  ## License
83
131
 
84
132
  MIT