Static Optional ReadonlySupportsWhether this driver subclass supports dynamic, multi-tool sets projected into the realtime session (e.g. direct action invocation on realtime co-agents).
Static capability descriptor read before a session instance exists.
Default: absent / false. Subclasses that support dynamic toolsets declare public static readonly SupportsDynamicToolSet = true;.
ProtectedapiOnly sub-classes can access the API key
The built-in default voices available in ElevenLabs — used to populate the voice picker.
ElevenLabs supports the client-direct topology natively: the signed websocket URL is a short-lived, agent-scoped credential the browser can open directly.
Whether this driver's sessions carry a video track in addition to audio — i.e. the model accepts video input (it can "see" the user's camera) and/or emits video output (a talking-head avatar / generated video), in sync with audio.
Defaults to false (audio-only — today's realtime models). Video-capable drivers (a native
multimodal realtime model, or an avatar provider) override this to true. The session's media
plane is media-tagged (IRealtimeSession.SendInput takes a RealtimeMediaKind;
IRealtimeSession.OnVideoOutput delivers video-out), so a video session reuses the entire
realtime contract — only the media frames gain a video kind. Resolution prefers a video-capable
model when an agent requests video, and degrades to audio-only otherwise.
true if sessions can carry video; false (audio-only) otherwise.
ProtectedbuildBuilds the create/update body for the managed agent: the placeholder base prompt, the
session tool set mapped to inline CLIENT tools (expects_response: true so the agent
blocks on — and then speaks — each result; max response timeout because MJ client tools
delegate to long-running agents), and the platform-settings enablement of every
per-session override the driver sends (buildRequiredOverrideEnablement).
params.Config.llm (when a string) selects the agent's underlying LLM; all other
provider-level agent settings are deployment concerns (use a verbatim agent id for full
control).
Optionalconfig: JSONObjectProtectedconnectTransport seam for the server-bridged conversation websocket. Production speaks the RAW
Agents protocol over the platform-global WebSocket (browsers / Node 22+) — the SDK's
high-level Conversation wrapper owns audio devices, which a server bridge must not.
Resolves once the socket is OPEN; unit tests override this to return an in-memory fake.
ProtectedcreateREST seam: creates an agent, returning its id.
Mints the client-direct config: ensures the managed agent, fetches a signed URL
(the ephemeral credential — connect window ≈ ELEVENLABS_SIGNED_URL_TTL_MS),
and packs the private pact SessionConfig the same-keyed 'elevenlabs' client
driver consumes: { agentId, overrides, config }, where overrides is the wire-shaped
conversation_config_override carrying the server-authored system prompt and config
passes params.Config through opaquely.
ProtectedensureResolves params.Model to a concrete ElevenLabs agent id.
agent_…) is returned as-is — the deployment owns that agent's
configuration (including its tool set and override enablement).OptionalExchangeOptional WebRTC SDP exchange seam for drivers that support server-brokered WebRTC topologies.
The local SDP offer generated by the browser.
Optional_sessionConfig: Record<string, unknown>The session configuration to initialize the session with.
The answer SDP, provider session ID, and any prebill accounting.
ProtectedfindResolves the managed agent by NAME, retrying a miss up to MAX_AGENT_LOOKUP_ATTEMPTS times.
ElevenLabs' agent search is EVENTUALLY CONSISTENT: an agent created moments ago — by us or by a concurrent process — is briefly invisible to find-by-name. Treating one miss as "does not exist" makes the ensure flow CREATE, forking a duplicate managed agent that then competes for the same name forever.
Cost falls entirely on the FIRST-EVER provision of a name. A name that resolves pays nothing (it hits on attempt 1); a name that genuinely does not exist yet exhausts every attempt before falling through to create, so it pays the whole backoff ladder — 500ms + 1000ms ≈ 1.5s at the current constants, once, before the create call. Every later session for that name is served from agentCache without any of this.
The managed agent name to resolve.
The adopted agent summary, or undefined once the attempts are exhausted.
ProtectedgetREST seam: fetches an agent's full configuration.
ProtectedlistREST seam: lists agents matching a name search.
ProtectedmintREST seam: mints the signed websocket URL for an agent
(GET /v1/convai/conversation/get-signed-url?agent_id=… with the xi-api-key header —
the SDK call below is that exact request).
ProtectedpauseBackoff between agent-lookup retries. Overridden in tests so they never sleep.
Opens a server-bridged conversation: ensures the agent, mints a signed URL, opens the
websocket (via the connectConversation seam), sends the
conversation_initiation_client_data frame carrying the per-session system prompt, and
resolves only once the server's conversation_initiation_metadata confirms the session
config is applied (driver obligation #7 — "ready only after the config is applied").
ProtectedupdateREST seam: PATCHes an agent's configuration.
StaticBuildBuilds the RAW-WIRE conversation_config_override for one session: the server-authored
system prompt, plus the per-session TTS voice and opening utterance when the config bag
carries them.
Mind the casing split. This object is forwarded VERBATIM onto the conversation
websocket (by this driver server-bridged, by the 'elevenlabs' client driver
client-direct), so it is snake_case — tts.voice_id, agent.first_message. The matching
ENABLEMENT in buildRequiredOverrideEnablement goes out through the SDK's serializer
instead and is therefore camelCase — tts.voiceId, agent.firstMessage. Only
agent.prompt.prompt reads the same in both, so every override added after it has to be
spelled twice, differently.
Each optional key is omitted ENTIRELY when unconfigured, so a session that configures
neither is byte-for-byte the frame it was before either existed. That is what preserves
the platform's wait-for-the-user default: an ENABLED-but-empty first_message means
exactly "no opening utterance", so omitting the key and sending it blank are the same
behavior — and omitting keeps the frame honest.
The per-session system prompt (the standing prompt override).
Optionalconfig: JSONObjectThe session's open config bag (realtime.voice.providers.elevenlabs merged in).
The wire-shaped override object.
StaticExtractExtracts the CLIENT tools from a fetched agent as Core tool definitions, for fingerprint comparison against the requested set. Non-client tools (webhook/system/mcp) are ignored — they are deployment-side additions the managed flow must not fight over.
StaticMapMaps a Core tool definition up to an ElevenLabs inline CLIENT tool config.
StaticModelThe desired-model-settings half of the ensure cache key (#3859): the config bag's llm
and temperature in one stable string. Unconfigured values serialize as absent, so a bag
that says nothing produces the same fingerprint forever.
Optionalconfig: JSONObjectStaticModelWhether the agent's OWN configuration matches the model settings the config bag asks for — the drift the ensure flow repairs for settings that are AGENT STATE rather than per-session overrides (#3859).
temperature lives here because the platform offers no per-session override flag for it
(PromptAgentApiModelOverrideConfig has prompt/llm/nativeMcpServerIds only), so the
only honest delivery is: set it on the managed agent's body and re-PATCH when it changes.
llm is checked here TOO, even though it is also per-session overridable, so an agent whose
configured default model changed is repaired even for callers that send no override.
An UNCONFIGURED desire matches anything: half the point of the managed agent is that the deployment may tune it, and a config bag that says nothing must not stampede a PATCH war against a hand-tuned value.
Optionalconfig: JSONObjectStaticOverridesWhether the agent's platform settings enable EVERY per-session override this driver sends — the drift condition the ensure flow repairs. Checking the whole required set (rather than the prompt override alone) is what lets an agent provisioned by an older MJ version pick up a newly-added override: it still matches on tools, so the enablement gap is the only signal that it needs a PATCH.
This is the READ half of the contract buildRequiredOverrideEnablement WRITES.
The two are deliberately spelled out separately rather than derived from one another: the
write side is the SDK's …Input shape and this reads the …Output shape, so a generic
walker over either would have to erase both types and lose exactly the compile-time
checking that catches a mistake here. They are kept honest instead by the round-trip test
"considers an agent this driver just provisioned already satisfied" — add an override to
one half only and that test fails (as a permanent re-PATCH loop).
The agent configuration as fetched from the REST API.
true when every required override is enabled.
StaticPickChooses ONE agent when the search returns several with the requested name — which happens whenever a duplicate was forked before this guard existed. The OLDEST wins (ties broken by agent id), so every process and every session converges on the SAME agent instead of picking whichever the API happened to list first and PATCHing them alternately.
The search results.
The exact name being resolved.
The canonical match, or undefined when none has that exact name.
StaticPromptWhether the agent's platform settings enable the per-session system-prompt override.
This is only ONE of the overrides the driver requires, so a true here does
NOT mean the agent is up to date — an agent predating per-session voice passes this while
still dropping the voice. Retained for API compatibility only; the ensure flow uses
ElevenLabsRealtime.OverridesSatisfied, and so should any caller.
StaticResolveReads the session's opening utterance out of the config bag — the firstMessage key, which
maps to ElevenLabs' agent.first_message override.
This is spoken VERBATIM, not interpreted. It is the literal first thing the agent says, not an instruction to the model about how to open — which is also why it is immune to the ordering of ElevenLabsRealtimeSession.SendInitiation's deferred initial-context injection: there is no generation to race.
A missing, blank, or non-string value yields undefined so the override is omitted rather
than sent empty. An empty first_message IS the platform's wait-for-the-user default, so
this is a no-op either way — omitting simply keeps the frame free of meaningless keys.
Optionalconfig: JSONObjectThe session's open config bag.
The trimmed opening utterance, or undefined when none is configured.
StaticResolveReads the DRIVER-NEUTRAL voice key out of the session's config bag — the same key the
AssemblyAI and Inworld realtime drivers read, so a persona's configured voice is authored
identically whichever provider ends up speaking it. Reached in practice via the effective
config's per-provider bag, realtime.voice.providers.elevenlabs.voice.
A missing, blank, or non-string value yields undefined so the override is omitted rather
than sent empty (ElevenLabs would reject an empty voice_id, killing the whole session
over a misconfigured persona).
Optionalconfig: JSONObjectThe session's open config bag.
The trimmed ElevenLabs voice id, or undefined when none is configured.
StaticToolCanonical, order-insensitive fingerprint of a tool set (same scheme as the Gemini realtime driver). A schema round-trip through ElevenLabs that fails exact equality merely triggers a harmless idempotent PATCH.
Real-time, full-duplex driver for the ElevenLabs Agents Platform, implementing the Core BaseRealtimeModel primitive. Registers as
ElevenLabsRealtimeand is resolved forMJ: AI ModelstypedRealtime(API-key env alias:AI_VENDOR_API_KEY__ElevenLabsRealtime).Why an "agent", not a model: ElevenLabs realtime is an orchestrated STT→LLM→TTS stack exposed only through pre-configured server-side agents — there is no bare-model realtime socket. The driver hides that behind the standard realtime contract with a managed-agent strategy (ensureAgent):
params.Modelstarting withagent_→ used VERBATIM as a deployment-managed agent id.Per-session prompt, voice AND opening-utterance authority stays with MJ: the managed agent stores only a placeholder prompt and explicitly enables the overrides in buildRequiredOverrideEnablement (
agent.prompt.prompt+agent.first_message+tts.voice_id); every session (server-bridged or client-direct) sends the real system prompt — plus its voice and first message, when configured — in itsconversation_initiation_client_dataframe. All three are per-session overrides, so sessions differing in any of them still share ONE managed agent.The agent can speak first only because
agent.first_messageis among those overrides: with none set, ElevenLabs waits for user audio before producing any, and no wording of the persona prompt changes that (issue #3557).Topologies:
No usage events: the Agents websocket reports no token usage; sessions never emit IRealtimeSession.OnUsage. Usage accounting for ElevenLabs realtime lives in the platform's own conversation dashboard / billing exports.