Adzen on CopilotKit — Developer Guide
Adzen sits alongside CopilotKit as AG-UI middleware. It intercepts the AG-UI event stream, matches ads to assistant output, and renders sponsored placements — all without modifying or blocking content.
The integration is additive only: Adzen adds adzen_placement custom events to the stream. The original event stream passes through unchanged with zero added latency to the text flow.
Package Exports
| Export path | Environment | Content |
|---|---|---|
@adzenai/ai | Any | Re-exports core types (AdzenPlacement, ProcessResponse, ProcessResponseAd, AdzenBaseConfig) |
@adzenai/ai/copilotkit | Server or client | AdzenAsyncMiddleware, AdzenAsyncConfig |
@adzenai/ai/copilotkit/react | Browser (React) | AdzenCard, useAdzenPlacement, dispatchPlacementEvent, dispatchPlacementEvents |
Public API Summary
Server-side / Middleware
| Export | Type | Description |
|---|---|---|
AdzenAsyncMiddleware | Class | AG-UI middleware that buffers text, calls the Adzen /process API, and emits adzen_placement custom events |
AdzenAsyncConfig | Interface | Configuration for AdzenAsyncMiddleware (apiKey, endpointUrl, timeoutMs, adUnitPosition, location, conversationId) |
React
| Export | Type | Description |
|---|---|---|
AdzenCard | Component | Renders a sponsored ad card for a given messageId. Returns null when no ad is available. Fires render/view impression beacons directly to the delivery API. |
useAdzenPlacement | Hook | Subscribes to adzen_placement browser events and maintains a Map<string, AdzenPlacement>. |
dispatchPlacementEvent | Function | Dispatches a single AG-UI adzen_placement event as a browser CustomEvent. |
dispatchPlacementEvents | Function | Dispatches all placement events from an array of AG-UI events. |
Types (from @adzenai/core)
| Type | Description |
|---|---|
AdzenPlacement | Ad placement data: ad_id, advertiser_name, headline, cta_text, destination_url, creative_url, adUnitPosition, render_impression_url, view_impression_url |
ProcessResponse | API response shape: message_id, ads array |
ProcessResponseAd | Raw ad object from the API: ad_id, title, cta, click_through_url, render_impression_url, view_impression_url, etc. |
AdzenBaseConfig | Shared base config: endpointUrl, apiKey, timeoutMs |
Architecture
AG-UI Event Flow
Backend Agent
│
│ AG-UI events (RUN_STARTED, TEXT_MESSAGE_START, TEXT_MESSAGE_CONTENT,
│ TEXT_MESSAGE_END, RUN_FINISHED, CUSTOM_EVENT, ...)
▼
AdzenAsyncMiddleware.processEvent(event)
│
├─ RUN_STARTED → captures threadId as conversation_id
├─ TEXT_MESSAGE_START → creates buffer for messageId
├─ TEXT_MESSAGE_CONTENT → appends delta to buffer
├─ TEXT_MESSAGE_END → triggers ad fetch, emits adzen_placement if matched
├─ CUSTOM_EVENT (final) → fallback content source for non-text agents
├─ RUN_FINISHED → waits for all pending ad fetches to settle
└─ all other events → passed through unchanged
│
▼
Downstream events (original events + adzen_placement custom events)
│
▼
dispatchPlacementEvents() → bridges to browser CustomEvents
│
▼
useAdzenPlacement() → maintains Map<messageId, AdzenPlacement>
│
▼
AdzenCard → renders ad, tracks viewability, fires impression GET beaconsAd Fetch Lifecycle
RUN_STARTEDcapturesthreadIdasconversation_id(if present).TEXT_MESSAGE_STARTcreates a buffer keyed bymessageId.TEXT_MESSAGE_CONTENTevents accumulate text in the buffer.TEXT_MESSAGE_ENDtriggers aPOSTto the Adzen/processAPI with the complete message text,message_id, and optionallylocationandconversation_id.- If the API returns ads, the middleware maps the first ad to
AdzenPlacementand appends anadzen_placementcustom event to the downstream array. - If the API returns an empty
adsarray, times out, or fails, no placement event is emitted. RUN_FINISHEDis held until all pending ad fetches settle viaPromise.allSettled.
Impression Tracking
AdzenCard fires two impression beacons as client-side GET requests directly to the delivery API:
- Render impression — fired on mount when the ad first becomes available.
GETtorender_impression_urlwithad_unit_positionandtimestampappended. - View impression — fired after the ad is fully visible in the viewport for the configured threshold (default 1000ms).
GETtoview_impression_urlwithad_unit_position,viewability_ms, andtimestampappended.
No server-side proxy or authentication headers are needed. The impression URLs are pre-built by the API with all required identifiers baked in. A single retry after 1 second is attempted on failure.
Ad Decision Boundaries
| Trigger | What is sent | When |
|---|---|---|
TEXT_MESSAGE_END | Complete buffered message text + message_id + location + conversation_id | After every assistant message |
Final CUSTOM_EVENT | Event payload content (output, result, data, payload, or message field) | Only if no text buffer was already fetched for that messageId |
The final custom event detection uses the same heuristic as production SDK integrations: the event must have type of CUSTOM_EVENT/CUSTOM/CustomEvent and either final: true, isFinal: true, or a name containing assistant_final or final_output.
Concurrent Message Handling
The middleware maintains a Map<string, MessageBuffer> keyed by messageId. This supports:
- Multiple concurrent assistant messages (multi-agent scenarios)
- Fallback ID generation when
messageIdis missing (__adzen_fallback_${timestamp}_${counter}) - Stale buffer eviction (entries older than
timeoutMs * 2are removed on the nextTEXT_MESSAGE_START)
Silent Degradation
Adzen is designed to be invisible on failure:
| Scenario | Behavior |
|---|---|
API returns empty ads array | No ad card rendered |
| API times out | No ad card rendered |
| API returns non-200 | No ad card rendered |
| Network error | No ad card rendered |
fetchAd throws | Caught silently, no ad card rendered |
The assistant text stream is never delayed or modified, regardless of ad API behavior.
Supported Runtime Conditions
| Requirement | Value |
|---|---|
| Node.js | >=18 |
| React | >=18 |
| AG-UI protocol | Any agent producing standard AG-UI events (RUN_STARTED, TEXT_MESSAGE_*, RUN_FINISHED, CUSTOM_EVENT) |
| CopilotKit | Compatible with @copilotkit/react-core and @copilotkit/runtime (no version lock — Adzen operates at the AG-UI layer) |
@adzenai/ai | Peer dependencies: @ag-ui/client, @ag-ui/core, rxjs, react (all optional) |
Next Steps
- Integration Walkthrough — step-by-step wiring guide
- Configuration — environment variables, adapter options, timeouts
- Run the Demo — see the integration in action
- Add Adzen to CopilotKit — quick-start for existing apps