SDKs
AI
CopilotKit
Developer Guide
Overview

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 pathEnvironmentContent
@adzenai/aiAnyRe-exports core types (AdzenPlacement, ProcessResponse, ProcessResponseAd, AdzenBaseConfig)
@adzenai/ai/copilotkitServer or clientAdzenAsyncMiddleware, AdzenAsyncConfig
@adzenai/ai/copilotkit/reactBrowser (React)AdzenCard, useAdzenPlacement, dispatchPlacementEvent, dispatchPlacementEvents

Public API Summary

Server-side / Middleware

ExportTypeDescription
AdzenAsyncMiddlewareClassAG-UI middleware that buffers text, calls the Adzen /process API, and emits adzen_placement custom events
AdzenAsyncConfigInterfaceConfiguration for AdzenAsyncMiddleware (apiKey, endpointUrl, timeoutMs, adUnitPosition, location, conversationId)

React

ExportTypeDescription
AdzenCardComponentRenders 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.
useAdzenPlacementHookSubscribes to adzen_placement browser events and maintains a Map<string, AdzenPlacement>.
dispatchPlacementEventFunctionDispatches a single AG-UI adzen_placement event as a browser CustomEvent.
dispatchPlacementEventsFunctionDispatches all placement events from an array of AG-UI events.

Types (from @adzenai/core)

TypeDescription
AdzenPlacementAd placement data: ad_id, advertiser_name, headline, cta_text, destination_url, creative_url, adUnitPosition, render_impression_url, view_impression_url
ProcessResponseAPI response shape: message_id, ads array
ProcessResponseAdRaw ad object from the API: ad_id, title, cta, click_through_url, render_impression_url, view_impression_url, etc.
AdzenBaseConfigShared 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 beacons

Ad Fetch Lifecycle

  1. RUN_STARTED captures threadId as conversation_id (if present).
  2. TEXT_MESSAGE_START creates a buffer keyed by messageId.
  3. TEXT_MESSAGE_CONTENT events accumulate text in the buffer.
  4. TEXT_MESSAGE_END triggers a POST to the Adzen /process API with the complete message text, message_id, and optionally location and conversation_id.
  5. If the API returns ads, the middleware maps the first ad to AdzenPlacement and appends an adzen_placement custom event to the downstream array.
  6. If the API returns an empty ads array, times out, or fails, no placement event is emitted.
  7. RUN_FINISHED is held until all pending ad fetches settle via Promise.allSettled.

Impression Tracking

AdzenCard fires two impression beacons as client-side GET requests directly to the delivery API:

  1. Render impression — fired on mount when the ad first becomes available. GET to render_impression_url with ad_unit_position and timestamp appended.
  2. View impression — fired after the ad is fully visible in the viewport for the configured threshold (default 1000ms). GET to view_impression_url with ad_unit_position, viewability_ms, and timestamp appended.

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

TriggerWhat is sentWhen
TEXT_MESSAGE_ENDComplete buffered message text + message_id + location + conversation_idAfter every assistant message
Final CUSTOM_EVENTEvent 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 messageId is missing (__adzen_fallback_${timestamp}_${counter})
  • Stale buffer eviction (entries older than timeoutMs * 2 are removed on the next TEXT_MESSAGE_START)

Silent Degradation

Adzen is designed to be invisible on failure:

ScenarioBehavior
API returns empty ads arrayNo ad card rendered
API times outNo ad card rendered
API returns non-200No ad card rendered
Network errorNo ad card rendered
fetchAd throwsCaught silently, no ad card rendered

The assistant text stream is never delayed or modified, regardless of ad API behavior.

Supported Runtime Conditions

RequirementValue
Node.js>=18
React>=18
AG-UI protocolAny agent producing standard AG-UI events (RUN_STARTED, TEXT_MESSAGE_*, RUN_FINISHED, CUSTOM_EVENT)
CopilotKitCompatible with @copilotkit/react-core and @copilotkit/runtime (no version lock — Adzen operates at the AG-UI layer)
@adzenai/aiPeer dependencies: @ag-ui/client, @ag-ui/core, rxjs, react (all optional)

Next Steps