AbstractProtectedactiveThe kind of the response currently in flight. Event ordering (confirmed against the live
OpenAI API): response.created → transcript deltas → *_audio_transcript.done →
response.done. The transcript-done frame therefore arrives while the kind is still set,
letting onAssistantDone classify the turn; response.done then resets it.
ProtectedclosedTrue once Disconnect ran — an expected socket close must not surface as fatal.
ProtectedconfirmedTrue while a response the provider has CONFIRMED (response.created seen, response.done not
yet) is in flight. Distinct from responseActive, which is set EAGERLY before a local
response.create is even sent: if the provider REJECTS that create (an error frame with no
response.created), responseActive would otherwise stay stuck true forever. This flag lets
onErrorFrame tell "the eager flag is a phantom for a rejected create" (no confirmed
response) from "a real response is genuinely active" (e.g. a concurrent VAD turn), so it clears
the phantom without disturbing a live turn. Set on response.created, cleared on response.done.
ProtectedcurrentThe client's own view of the session state — mirrors what emitStateChange last
reported, EXCEPT after a tool call is emitted: the host typically shows its own busy
state then, so the client silently leaves 'speaking' (no emission) to preserve the
host's indicator until the result reply starts (see onToolCallFrame).
ProtectedinputMeter over the USER's microphone, when the driver attached one.
ProtectedmicThe mic-capture pipeline streaming PCM16 up to the provider.
ProtectedmicThe mic stream owned by the current connection (used by the shared SetMuted).
ProtectedoutputMeter over the AGENT's audio output, when the driver attached one.
ProtectedpendingAccumulates the in-flight assistant transcript across delta frames.
ProtectedpendingCount of locally-initiated response.creates whose response.created echo has not arrived
yet. While non-zero, a response.done belongs to an EARLIER (typically just-cancelled)
response — it must not clear the busy flag, flush the queued trigger, or flip the state for
the response we just started (usage is still emitted). Consumed by response.created.
ProtectedpendingSet by RequestSpokenUpdate just before it sends its response.create, and
CONSUMED by the very next response.created frame, which stamps
activeResponseKind for that turn. Narration is only requested while the model is
idle (hosts gate on IsBusy), so under normal ordering the next response.created
is ours.
ProtectedpendingSet when a tool result is ready while a response is active; sent on the next response.done.
ProtectedplaybackThe local playout engine (client-owned audio plane).
ProtectedresponseTrue while the model has a response in flight; gates narration + queues the tool result.
ProtectedsessionThe wire-shaped session object applied via session.update at the readiness boundary.
Protected so test subclasses can seed it without a full Connect.
ProtectedsocketThe live socket (null when disconnected). Protected so test subclasses can inspect.
ProtectedsocketTrue while the socket is OPEN — gates every send so a CONNECTING socket never throws.
ProtectedtracksTracks requested, established, or denied on this session.
All tracks evaluated for this session (including 'unsupported' or 'denied').
ProtectedconnectConnect-phase deadline in milliseconds, covering socket-open AND (when the provider
defers) the session.created wait. On expiry Connect rejects with a fatal error
instead of hanging forever against a silent endpoint. Override per driver/deployment.
Established tracks on this session (tracks in the 'live' state).
The frame rate (frames per second) the live inbound video track was negotiated at — the model's
ceiling or the session's request, whichever is lower — or undefined when no inbound video track
is live or none declared a rate. Anything pacing a video feed reads THIS, never a constant of its own.
true while a model response is in flight (generation started and not yet done).
Distinct from IsAudioPlaying: generation runs ahead of playback.
Hosts use this to gate interim narration so it never interrupts a reply.
How many concurrent inbound video streams the model accepts: the ceiling the driver declared when it
negotiated tracks, else 1 when an inbound video track is live and 0 when it is not.
A source arbiter reads this to decide whether several live video sources must be narrowed to one (with a note to the model on each switch) or passed through untouched.
ProtectedproviderDebug label used in the shared console diagnostics.
ProtectedreleasesWhether the model takes inbound video on this session: the driver declared an inbound video track it supports when it
negotiated tracks, and a stream limit above zero. No video track need be live; one is added when a capture starts
(AddTrack). false before tracks are negotiated, and for a driver that does not negotiate them.
ProtectedSupportsWhether this driver can open a relay session (ClientRealtimeSessionConfig.Transport 'relay'): connect to
MJAPI's realtime relay at the session's RelayUrl rather than to its provider. false here; a driver that speaks
its provider's protocol through the relay opts in by overriding it (the Gemini Enterprise client does).
ProtectedunexpectedThe fatal-error message surfaced when the socket closes unexpectedly. Providers brand this.
ProtectedwaitsWhether the readiness boundary waits for the endpoint's session.created frame before
applying the session config (HuggingFace) or applies it on socket open (xAI — the
protocol has no separate readiness ack there).
Adds a track to a running session, such as inbound video when the user starts their camera, resolved against
what the driver supported when it negotiated at connect. The new track is 'live', or 'unsupported' with the
reason (the model takes no such track, or no more inbound video streams). A live track with the same
direction, modality and source is returned as it is. Other tracks keep their ids.
The base re-negotiates locally, which is all a driver needs when its provider accepts the media without a setup change (Gemini Live takes video frames at any time). A driver whose wire must change overrides this and calls it.
The track, or null when the session has not negotiated tracks: before it connects, or with a driver
that does not negotiate them (only the Gemini driver does today).
ProtectedapplySends the server-controlled session config (instructions + tools) as a session.update
so the co-agent's identity and tool set apply. Skipped when the pact carried no config
(e.g. the host failed to parse the server payload — it already logged that; sending an
EMPTY session.update would be wrong).
ProtectedAssertRefuses a session this driver cannot open: a relay session, unless the driver opts in through SupportsRelayTransport. Every driver calls this first in Connect, before it touches the session's media or opens a connection (driver obligation #11). The message names the provider key and never quotes the relay URL, which carries the relay's ticket.
The session the host asked this driver to open.
ProtectedattachAttaches (replacing + closing any previous) the USER-microphone meter. null detaches.
ProtectedattachAttaches (replacing + closing any previous) the AGENT-output meter. null detaches.
ProtectedbuildThe response.create frame for one spoken update — carrying the session identity, not
replacing it.
⚠️ response.instructions IS A FULL OVERRIDE OF THE SESSION SYSTEM PROMPT for that one
response, not an addition to it. That is the whole reason this method exists. The
server-side twin of this driver (openAIRealtime.ts) already states the rule and guards
the BLANK case on it — forwarding '' "would wipe the co-agent identity framing" — but the
NON-blank case was never guarded, so every caller that supplied a real direction wiped it
just as thoroughly and much less visibly.
What that cost, live: the one turn that rides this method had no persona, so the model fell back to its vendor default and opened a hiring interview with "I'm ChatGPT, your friendly voice companion" while every other turn in the same session correctly said "I'm Sam Rivera, Support Team Lead" (bizapps-caliber#397 / MJ#4591). Every commentary-style caller is exposed the same way — an opening turn, a silence check-in, a delegation narration — and none of them should have to restate an identity the session already carries.
Three cases, and the middle one is the fix:
response.create. "Respond now, under the session prompt"
(the meeting-mode bridge trigger passes ''). Matches the server twin exactly.identity, then direction. The identity leads
because it is context, and the direction is LAST because it is the thing being asked for
now — the most recent line is the one a model weights hardest.response.cancel (only when a response is actually in flight) and silences
already-generated speech via the transport's stopAudioOutput. Resets the local
response state machine (active flag, narration kind, accumulated transcript) but
PRESERVES any queued tool-result trigger: delegated work is never affected by a
floor-control cancel, and the queued trigger still fires on the cancelled response's
trailing response.done. No-op when idle or when the transport is not open.
ProtectedcanProtectedclearDriver hook for Disconnect: drops the stream AND the handlers. Handlers must not survive
into a later Connect on a reused instance, or a stale host callback would receive the
next session's stream.
ProtectedcloseReleases both meters — every driver calls this from its disconnect/teardown path.
Opens the websocket, gates on open (and session.created when the provider defers),
applies the server-authored session config as the FIRST frame (prompt + tool authority
stay server-side), builds the PCM audio plane at the provider's rate, and reports
'listening' only after all of that (obligation #7).
ProtectedcreateCreation seam for the mic-capture pipeline at the provider's rate. Production delegates
to the shared createPcmMicCapture; unit tests override this with a no-op fake
(and may capture onPcmChunk to simulate mic frames).
ProtectedcreateCreation seam for the playout engine. Production returns the shared RealtimePcmPlayback.
Protectedcurrentthis transport's pact is sessionObject, applied over the socket.
Tears down the socket, mic capture, mic tracks, and playout engine, resets the response
state machine, and emits a final 'closed' (unless already 'error'). Safe to call
more than once.
ProtectedemitProtectedemitEmits a true barge-in interruption to the registered handler (if any).
ProtectedemitEmits the model/avatar's remote video to the registered handler (video drivers only). A driver with a plain
MediaStream passes it as it is; it is handed on as a 'stream' source.
ProtectedemitEmits a state change to the registered handler (if any).
ProtectedemitEmits a tool-call request to the registered handler (if any).
ProtectedemitEmits a track state change to the registered handler (if any).
ProtectedemitEmits a transcript event to the registered handler (if any).
ProtectedemitEmits a usage update (token deltas for a completed response/turn) to the registered handler (if any).
ProtectedemitEmits a user-speech-onset to the registered handler (if any).
ProtectedflushOn a turn completing, fire any queued tool-result response so the answer is spoken.
ProtectedformatFormats a socket-level error message for the host. Providers brand this (see xAI).
The session's current audible activity, or null when this driver attached no
meters at all (hosts then keep their turn-state-driven visuals). Sampled by hosts
per animation frame — implementations are cheap, allocation-light reads of an
AnalyserNode; no per-call provider traffic.
Returns the AGENT's remote-audio MediaStream when this driver owns a tappable
agent-audio plane (a WebRTC driver's remote track, or a PCM driver's playout output),
or null otherwise. Hosts use it to MIX the agent's voice into a browser-side recording
alongside the mic; null (drivers with no tappable plane, or before the stream lands)
degrades gracefully to mic-only capture. Hosts may still call it as client.GetRemoteMediaStream?.().
Pure read — never mutates.
ProtectedhandleProtectedhandleDispatches a typed OpenAI-protocol server event to the appropriate behavior.
ProtectedhandleParses one raw inbound payload and dispatches it. Non-JSON frames and non-object JSON values are ignored (with the onNonJsonFrame diagnostic hook for the former).
ProtectedhandleA socket close the CONSUMER didn't ask for. Default: FATAL — providers hard-close at token expiry and when they end the session themselves, so an unexpected close is how credential / session death reaches the host (obligation #6). Providers with benign close semantics (a self-hosted proxy hop) override.
ProtectedhandleSurfaces a fatal socket error and marks the session unusable (obligation #6).
Returns whether a track of the requested modality and direction is established and 'live'.
ProtectedlogDiagnostic hook for each parsed inbound event. Default: silent.
ProtectedlogDiagnostic hook for each outbound event. Default: silent.
ProtectednegotiateNegotiates requested tracks against model capability. Audio (inbound + outbound) is the baseline floor of every realtime session; any additional requested tracks (such as channel video) are unioned onto this baseline floor.
OptionalmaxInboundVideoStreams: numberRegisters the (single) error handler. Receives both fatal transport errors (which are
also followed by an 'error' state change) and non-fatal provider error frames.
Registers the (single) interruption handler.
True barge-in only: fires ONLY when USER INPUT CUT OFF ACTIVE MODEL OUTPUT — a model response in flight or audio audibly playing when the user took the floor. A user simply taking their normal turn while the model is idle is NOT an interruption, and drivers must not report it as one (e.g. a raw "speech started" frame must be gated on whether a response is actually active or audio is playing).
On interruption, drivers must also flush locally-owned playback and report
IsAudioPlaying === false promptly (driver obligation #3). Hosts typically
use this hook to abort in-flight delegated work so a stale result is never narrated
into a conversation that has moved on.
ProtectedonDiagnostic hook for each non-JSON inbound frame. Default: silent.
ProtectedonWebRTC-only playback-buffer hooks; websocket transports never receive these frames.
ProtectedonRegisters a handler invoked when the agent's remote-audio stream becomes available —
IMMEDIATELY (synchronously) if it has already landed, otherwise when the driver publishes
it (a WebRTC track typically lands AFTER Connect resolves). Lets a host attach the
agent voice to a recording that began before the stream landed. Handler errors are
isolated (logged) so a host bug never disturbs the call. Handlers are session-scoped:
clearRemoteMediaStream (called from Disconnect) drops them.
Registers the (single) remote-VIDEO handler — the model/avatar's video for a VIDEO session (a talking-head the host renders, e.g. as the agent's tile). Invoked once the provider's video is available.
The video is a MediaVideoSource: a live stream (a WebRTC track), or a player that must own the
<video> element (MSE or WebCodecs playout of encoded avatar video). Hosts show either with
AttachVideoSource.
Optional capability: audio-only drivers (the default) never emit — registering is always safe,
but hosts must not assume video arrives. A driver whose model sends video (the server driver declares an
outbound video track in its session's Capabilities.SupportedOutboundTracks, or renders an avatar,
BaseRealtimeModel.SupportsAvatarOutput) calls emitRemoteVideo when it is live.
Invoked with the remote video when it becomes available.
ProtectedonCalled when the model takes the floor (response.created), i.e. the user's turn is definitively
over. Drivers that track per-user-turn transcription state override this to reset it. No-op here.
ProtectedonProtectedonRegisters the (single) state-change handler. States may be re-emitted (e.g. repeated
'listening' on successive barge-ins); hosts should treat emissions as idempotent.
Registers the (single) tool-call handler. The host executes the tool and calls SendToolResult with the outcome.
Registers the track-state-change handler — invoked when tracks transition in lifecycle
(e.g. established to 'live', 'denied', or 'unsupported').
Registers the (single) transcript handler. Receives interim deltas (IsFinal: false)
and final turn transcripts (IsFinal: true) for both roles, tagged with their
RealtimeClientTranscript.Kind.
Registers the (single) usage handler.
Emissions carry token deltas for the response/turn that just completed (see
RealtimeClientUsage — deltas preferred; cumulative-only providers must convert
in the driver). Hosts accumulate and relay/persist on their own cadence (e.g. the voice
session service debounces a RelayRealtimeUsage mutation onto the co-agent prompt run).
Optional capability: drivers whose provider exposes no usage telemetry simply never emit — registering a handler is always safe, but hosts must not assume emissions arrive. See RealtimeClientUsage for per-driver availability.
Registers the single handler invoked when the provider's voice-activity detection saw the user start to speak. Fires once per detected onset and may fire again inside one turn after a pause; it carries no text. Unlike OnInterruption, it fires whether or not the model was speaking.
ProtectedonEmits the user's spoken-input transcription. Default (OpenAI/HuggingFace): each
.completed frame is one final caption. Providers that STREAM the completed event
(Grok re-sends the full growing text each time) override to collapse the stream into a
single in-place-updating bubble.
Protected AbstractopenOpens the provider socket for this connection (drivers own URL/auth specifics).
ProtectedpublishDriver hook: records the agent's remote-audio stream and fans it out to every registered
handler. Passing null (a driver with no tappable plane) just records null and
notifies nobody.
Ends a track on a running session, such as inbound video when the user stops their camera: the track is
reported 'ended' and leaves AllTracks. Audio, the session's floor, cannot be removed.
Whether a track was ended.
Rebinds the PCM capture and the input meter to the stream's current track (obligation #10); the socket stays open.
ProtectedrequestAsks the model to speak (a tool result or typed-text reply) — immediately if it's idle,
otherwise queued until the current response finishes. An immediate trigger also
CONSUMES any queued trigger debt: every payload item is already in the conversation, so
one response.create voices everything (e.g. typed text barging in over a narration
that had tool results queued behind it).
Triggers ONE short spoken update with the given instructions. Marks the upcoming
response as 'narration' (flag consumed by the next response.created) so its
transcripts are emitted with Kind: 'narration' — ephemeral by contract. Sets
responseActive eagerly so a tool result landing mid-narration queues instead of
colliding.
Skips when busy (base-contract collision rule — drivers MUST queue or skip): a
response.create sent while a response is in flight would be rejected/garbled by the
provider, and narration is disposable by contract, so the update is dropped with a debug
log rather than queued to come out late and stale. Hosts SHOULD still gate on
IsBusy / IsAudioPlaying for timing quality.
The wire frame is built by buildSpokenUpdateEvent, which is where the identity rule lives — see that method for why the caller's direction is never sent on its own.
ProtectedresetResets the per-session response state machine (used on Disconnect).
Protected AbstractresolveResolves the PCM sample rate for the audio plane (both directions).
ProtectedresolveExtracts the wire-shaped session object from the server pact. Default: the pact minus the
client-only hints (ToProviderSessionConfig).
Injects a system-role context item the model can draw on the next time it speaks, WITHOUT forcing a reply. Item creation is always safe mid-response.
NOTE: role must be 'system' — gpt-realtime (and the compatible endpoints) reject 'developer' items ("Developer messages are only supported for quicksilver sessions").
ProtectedsendLogs (diagnostic hook) and delivers one client event when the transport is open.
ProtectedsendStreams one base64 PCM16 mic chunk as an input_audio_buffer.append frame.
ProtectedsendDelivers one protocol client event over the transport (only called when canSendEvents).
Injects typed text as a user-role message conversation item, then triggers a reply
through the SAME collision-safe path tool results use (requestResultResponse).
No-op when the transport isn't open.
SendText implies barge-in (base-contract rule): an active spoken response is cancelled via CancelActiveResponse before the text is injected, so the typed turn takes the floor immediately instead of waiting behind stale speech. When nothing is active the cancel is a no-op and the reply triggers immediately.
Sends the tool result back as a function_call_output conversation item, then triggers
a reply — immediately if the model is idle, otherwise queued until the current response
(e.g. a progress narration) finishes. Without the queueing the result's response.create
would collide with an in-flight narration and be dropped, leaving the model silent when
delegated work comes back.
OptionalSendStreams one video frame as base64-encoded image data to the model. Optional capability — drivers that support inbound video tracks implement this.
Base64-encoded image data.
OptionalmimeType: stringImage MIME type (defaults to 'image/jpeg').
OptionalsourceId: stringWhich source this frame came from. A driver whose model takes ONE inbound video
stream ignores it (the source arbiter already chose what to send); a driver whose model takes
several routes the frame to the track bound to that SourceID.
true if accepted and sent; false if dropped (throttled, unestablished, etc.).
Mutes / unmutes by toggling the mic tracks' enabled flag: the transport stays up and
streams SILENCE while muted (the provider's VAD sees a continuous stream and the un-mute
is glitch-free — the same policy across the client driver family).
ProtectedsetUpdates the client's own state view and emits the change to the host.
ProtectedstopProtected StaticreadReads a session pact's instructions, or null when it carries none worth preserving.
The shared websocket + client-owned-PCM transport for OpenAI-protocol client drivers (xAI Grok Voice, self-hosted HuggingFace). Extends the protocol brain with everything the websocket drivers previously each owned:
input_audio_buffer.appendframes up, shared playout engine scheduling base64 deltas down, audio meters both ways.IsAudioPlaying→ the playout clock.Concrete drivers supply only: the socket target (openProviderSocket), the session object + sample rate resolution from the server pact (resolveSessionObject / resolveSampleRate), and any provider behavior overrides (streamed user transcripts, close semantics).