SDKs
AI
CopilotKit
Developer Guide
Configuration

Configuration

The CopilotKit integration is configured through the AdzenAsyncConfig object passed to the AdzenAsyncMiddleware constructor. Values can be sourced from environment variables, explicit code, or a combination.

Configuration Precedence

  1. Explicit values passed to new AdzenAsyncMiddleware({ ... })
  2. Environment variables read by your application
  3. SDK defaults for optional fields

There is no automatic environment variable binding — your application code reads environment variables and passes them to the constructor.

Environment Variables

These are recommended environment variable names. Your application reads them and passes them to the middleware.

Required

VariablePurpose
ADZEN_API_KEYAdzen API key for your publisher account

Optional

VariableDefaultPurpose
ADZEN_API_URLhttps://api.adzen.ai/v1/aiAdzen API base URL. Override only if using a non-production environment.
ADZEN_TIMEOUT_MS3000Maximum time (ms) to wait for the Adzen /process API before abandoning the ad fetch
ADZEN_AD_UNIT_POSITION"chin"Ad unit position label used in tracking
ADZEN_LOCATIONDMA location code (e.g. "US-CA-803") sent with /process requests for geo-targeting

Example .env

ADZEN_API_KEY=adz_live_abc123
ADZEN_TIMEOUT_MS=3000
ADZEN_AD_UNIT_POSITION=chin
ADZEN_LOCATION=US-CA-803

Demo-Only Variables

These variables are used only by the built-in demo app (pnpm demo). They are not part of the SDK's runtime configuration.

VariableRequiredDefaultPurpose
OPENAI_KEYNoWhen set, the demo uses OpenAI gpt-4.1-mini for streaming agent responses instead of canned mock responses.
ADZEN_KEYNoWhen set, the demo calls the real Adzen API at https://api.adzen.ai/v1/ai/process instead of the mock ad API. The publisher profile is inferred from the key.

Create a .env file in packages/ai/src/copilotkit/demo/ to set these. See Run the Demo for the full behavior matrix.

Adzen API Response Format

The Adzen /process API returns:

{
  "message_id": "msg_abc123",
  "ads": [
    {
      "ad_id": 1071,
      "title": "Local Coffee Co",
      "cta": "Learn More",
      "click_through_url": "https://api.adzen.ai/dea/redirect/abc1234",
      "advertiser_name": "Acme Corp",
      "sponsor_logo_url": "https://example.com/logo.png",
      "creative_url": "https://example.com/banner.jpg",
      "description": "A great product",
      "pricing_model": "CPC",
      "relevancy_score": 0.95,
      "placement": "after_message",
      "pixel_trackers": [],
      "render_impression_url": "https://delivery.adzen.ai/v1/ai/impressions/render?ad_id=1071&message_id=msg_abc123&conversation_id=conv_7f3a9b2e",
      "view_impression_url": "https://delivery.adzen.ai/v1/ai/impressions/view?ad_id=1071&message_id=msg_abc123&conversation_id=conv_7f3a9b2e"
    }
  ]
}

The middleware consumes this format directly and maps it to AdzenPlacement internally:

API fieldSDK field
ads[0].ad_idad_id (coerced to string)
ads[0].titleheadline
ads[0].ctacta_text
ads[0].click_through_urldestination_url
ads[0].advertiser_nameadvertiser_name
ads[0].sponsor_logo_urladvertiser_image_url
ads[0].creative_urlcreative_url
ads[0].placementadUnitPosition
ads[0].render_impression_urlrender_impression_url
ads[0].view_impression_urlview_impression_url
ads.length === 0No placement event emitted

AdzenAsyncConfig Options

Passed to new AdzenAsyncMiddleware(config):

OptionTypeRequiredDefaultDescription
apiKeystringYesAdzen API key. Sent as the X-API-Key header.
endpointUrlstringNo"https://api.adzen.ai/v1/ai"Adzen API base URL. The middleware appends /process when calling the ad matching endpoint.
timeoutMsnumberNo3000Maximum time (ms) to wait for the /process API response. Uses AbortController to cancel the fetch on timeout.
adUnitPositionstringNo"chin"Ad unit position label included in impression beacons.
locationstringNoDMA location code sent as a top-level field on the /process request for geo-targeting.
conversationIdstringNoConversation identifier sent as conversation_id on the /process request. Links impressions to conversation context. If a RUN_STARTED event has a threadId, it overrides this value.

Recommended Setup

import { AdzenAsyncMiddleware } from "@adzenai/ai/copilotkit";
 
const adzen = new AdzenAsyncMiddleware({
  apiKey: process.env.ADZEN_API_KEY!,
  location: process.env.ADZEN_LOCATION,
});

With Conversation Tracking

const adzen = new AdzenAsyncMiddleware({
  apiKey: process.env.ADZEN_API_KEY!,
  location: "US-CA-803",
  conversationId: session.conversationId,
});

If you pass AG-UI RUN_STARTED events through the middleware and they contain a threadId field, the middleware automatically uses that as the conversation_id — overriding any value set in the config.

AdzenCardProps Options

Passed to <AdzenCard>:

PropTypeRequiredDefaultDescription
messageIdstringYesThe assistant message ID to look up an ad placement for.
adUnitPositionstringNo"chin"Ad unit position label appended to impression beacon URLs.
viewabilityThresholdMsnumberNo1000Milliseconds the ad must be continuously visible before a view impression fires.
classNamestringNoCSS class applied to the card's root <div>.

Example

<AdzenCard
  messageId={msg.id}
  adUnitPosition="chin"
  viewabilityThresholdMs={2000}
  className="my-ad-card"
/>

useAdzenPlacement Return Type

interface UseAdzenPlacementReturn {
  getAdForMessage(messageId: string): AdzenPlacement | null;
  placements: Map<string, AdzenPlacement>;
}
PropertyDescription
getAdForMessageReturns the AdzenPlacement for a given messageId, or null if no ad was matched.
placementsThe full Map of all received placements, keyed by messageId.

Bridge Functions

dispatchPlacementEvent(event)

Checks whether a single AG-UI event is an adzen_placement custom event and dispatches it as a browser CustomEvent on window. Returns true if the event was dispatched, false otherwise.

dispatchPlacementEvents(events)

Convenience wrapper that iterates an array of AG-UI events and calls dispatchPlacementEvent for each. Safe to call on every batch of events from processEvent() — non-placement events are silently ignored.

Both functions require a browser environment (window must be defined). They are no-ops in server-side contexts.

Viewability Configuration

Viewability tracking uses IntersectionObserver via @adzenai/core's observeViewability():

SettingDefaultDescription
Intersection threshold1.0The ad must be 100% visible in the viewport.
Time threshold1000msThe ad must be continuously visible for this duration.
Firing behaviorOnceThe impression fires once per ad instance.

The intersection threshold is not configurable via AdzenCard — it always requires full visibility. The time threshold is configurable via viewabilityThresholdMs.

Impression Beacons

AdzenCard fires two impression beacons as client-side GET requests directly to the delivery API. No server-side proxy or authentication is required.

Render impression — fired on mount (when the ad first becomes available). Sent as a GET to the pre-built render_impression_url with additional query parameters:

GET {render_impression_url}&ad_unit_position=chin&timestamp=2026-07-06T20:30:00.000Z

View impression — fired after the viewability threshold is met. Sent as a GET to the pre-built view_impression_url with additional query parameters:

GET {view_impression_url}&ad_unit_position=chin&viewability_ms=1000&timestamp=2026-07-06T20:30:01.000Z

Parameters Appended by the SDK

ParameterTypeBeaconsDescription
ad_unit_positionstringBothWhere the ad is rendered in the UI (e.g. "chin")
timestampstringBothISO 8601 timestamp of the event
viewability_msnumberView onlyThe viewability threshold in milliseconds

Null Impression URLs

If render_impression_url or view_impression_url is null (impression tracking not configured for this ad), the corresponding beacon is skipped entirely.

Error Handling and Silent Degradation

Adzen never throws errors into the AG-UI event stream or the React rendering tree. All failures are silent:

ScenarioBehavior
/process API returns non-200fetchAd returns null → no placement event
/process API times outAbortController cancels the request → no placement event
Network error during ad fetchcatch block returns null → no placement event
/process API returns empty ads arrayNo placement event emitted
Impression beacon fails (render or view)Single retry after 1 second; failure is swallowed
window undefined (SSR)dispatchPlacementEvent returns false — no-op

Timeout and Retry Policy

Ad Fetch

  • Timeout: Controlled by timeoutMs (default 3000ms). Uses AbortController to cancel the fetch.
  • Retries: None. A timed-out or failed ad fetch is abandoned. The ad slot for that message is forfeited.

Impression Beacon

  • Timeout: No explicit timeout on the impression beacon fetch.
  • Retries: One automatic retry after 1 second on failure. If the retry also fails, the error is swallowed.

RUN_FINISHED Hold

RUN_FINISHED events are held until all pending ad fetches settle via Promise.allSettled. This ensures the stream does not terminate before ad placements are emitted. The hold duration is bounded by the timeoutMs of the slowest pending fetch.

Stale Buffer Eviction

On each TEXT_MESSAGE_START, the middleware evicts any buffer entries older than timeoutMs * 2. This prevents memory leaks from incomplete message sequences (e.g., a TEXT_MESSAGE_START without a corresponding TEXT_MESSAGE_END).

Next Steps