Member Junction
    Preparing search index...

    AssemblyAI Voice Agent implementation of BaseRealtimeClient: a browser-direct agent websocket authenticated with the server-minted ONE-TIME client token (the EphemeralToken — no API key ever reaches the browser).

    Registered with the ClassFactory under the key 'assemblyai' — the Provider string the server's AssemblyAIRealtime driver stamps on its ClientRealtimeSessionConfig.

    Owns ALL AssemblyAI wire concerns (the behavioral sibling of the ElevenLabs client driver — the audio plane is client-owned over a websocket, no WebRTC):

    • Connect handshake: open wss://…/v1/ws?token=… → send the server-authored session.update (from the SessionConfig pact: system prompt, tools, voice, turn detection) as the FIRST frame → wait for session.ready → build the audio plane at the provider's FIXED 24 kHz PCM16 format → 'listening'. The state is gated on session.ready (driver obligation #7): only then has the provider confirmed the session config is applied (audio sent earlier would be dropped anyway).
    • Audio in: mic PCM16 at 24 kHz via the shared createPcmMicCapture worklet pipeline, streamed as base64 input.audio frames.
    • Audio out: reply.audio chunks decoded into the shared RealtimePcmPlayback at 24 kHz; IsAudioPlaying comes from its playout clock.
    • Transcripts: user turns stream as transcript.user.delta fragments (IsFinal: false deltas) finalized by transcript.user; agent turns arrive as ONE final transcript.agent (after a barge-in it carries interrupted: true and the text already reflects the truncated turn — no ElevenLabs-style correction event follows).
    • Busy mapping: IsBusy is set on reply.started / first reply.audio (and eagerly when this client triggers a reply) and cleared on reply.done and on a tool.call frame (the agent has yielded the floor pending the result — deadlock guard, obligation #2).
    • Narration is NATIVE: RequestSpokenUpdate sends reply.create with the instructions (a real per-response-instructions channel — no user-turn emulation). The response kind is stamped 'narration' at send time (sends are the turn triggers) and reset on the reply boundary; the kind tagging assumes transcript.agent precedes its reply.done, which is the provider's emission order.
    • SendText is EMULATED via reply.create: the protocol accepts no typed user input, so the typed text rides as response instructions framing it as the user's turn. Fidelity caveat: the text enters as instructions rather than a conversation user turn, so the agent may occasionally reference "the message you sent" — hosts should treat typed input on this provider as best-effort.
    • SendContextNote is EMULATED via the mutable system_prompt: notes are appended under a "Background updates" heading and the full prompt is re-sent via session.update — a config write that never triggers or disturbs generation, so it is sent immediately even mid-reply.
    • Barge-in: input.speech.started while a reply is active (or audio audibly playing) is the snappy flush point (per the provider's own guidance — ~300 ms faster than waiting for reply.done); reply.done with status: 'interrupted' is the authoritative verdict and is the fallback flush when the speech-start gate missed. A speech start while idle is a user simply taking their turn — NOT an interruption (base contract).
    • Cancel: the protocol has no cancel frame. CancelActiveResponse flushes the locally-owned playout queue (speech stops immediately), marks the reply inactive, and SUPPRESSES residual reply.audio frames of the cancelled reply until the next reply boundary — so late-arriving chunks of a cancelled turn are never played or allowed to re-assert 'speaking'.
    • No usage telemetry: the streaming socket exposes no token-usage events, so this driver NEVER emits OnUsage (registering a handler is safe; it just never fires).

    Hierarchy (View Summary)

    Index

    Constructors

    Properties

    inputAudioMeter: IRealtimeAudioMeter = null

    Meter over the USER's microphone, when the driver attached one.

    outputAudioMeter: IRealtimeAudioMeter = null

    Meter over the AGENT's audio output, when the driver attached one.

    tracks: RealtimeTrack[] = []

    Tracks requested, established, or denied on this session.

    Accessors

    • get InboundVideoRate(): number

      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.

      Returns number

    • get IsAudioPlaying(): boolean

      Returns boolean

      directly from the playout engine's playhead clock — this client OWNS the output buffer, so "audibly playing" is precisely "scheduled audio extends beyond the audio context's current time".

    • get MaxInboundVideoStreams(): number

      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.

      Returns number

    • get SupportsInboundVideo(): boolean

      Whether 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.

      Returns boolean

    • get SupportsRelayTransport(): boolean

      Whether 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).

      Returns boolean

    Methods

    • 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.

      Parameters

      Returns RealtimeTrack

      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).

    • Returns void

      has no cancel frame — the client OWNS the audio plane, so cancelling means: flush the local playout queue (speech stops immediately), mark the in-flight reply inactive, SUPPRESS residual reply.audio chunks of the cancelled reply until the next reply boundary, and drain queued sends (a queued narration takes the floor next — delivery is never dropped by a cancel). No-op when nothing is active.

    • Creation seam for the mic-capture pipeline at the provider's fixed 24 kHz rate. Production delegates to the shared createPcmMicCapture; unit tests override this with a no-op fake (and may capture onPcmChunk to simulate mic frames).

      Parameters

      • micStream: MediaStream
      • sampleRate: number
      • onPcmChunk: (base64Pcm16: string) => void

      Returns Promise<IPcmMicCapture>

    • 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.

      Returns MediaStream

    • 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.

      Parameters

      • handler: () => void

      Returns void

    • Registers 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.

      Parameters

      • handler: (stream: MediaStream) => void

      Returns void

    • 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.

      Parameters

      • handler: (video: MediaVideoSource) => void

        Invoked with the remote video when it becomes available.

      Returns void

    • 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.

      Parameters

      Returns void

    • Triggers ONE short spoken update — NATIVE via reply.create with per-response instructions. The resulting reply is stamped Kind: 'narration' at send time (reset on the reply boundary). Queued behind any in-flight reply per the base contract's collision rule (an overlapping reply.create would collide with active generation).

      Parameters

      • instructions: string

      Returns void

    • Injects typed text — EMULATED via reply.create (the protocol accepts no typed user input), with instructions framing the text as the user's turn. No-op when the session is not open. See the class-level fidelity caveat.

      SendText implies barge-in (base-contract rule): an active spoken reply is cancelled via CancelActiveResponse first — playback flushed, reply marked inactive, queued sends drained — so the typed turn takes the floor immediately. If a drained queued send starts a new reply, the text queues behind it, preserving delivery order.

      Parameters

      • text: string

      Returns void

    • Feeds an executed tool's result back via tool.result, correlated by the provider's call_id. EXACTLY-ONCE: the pending id is consumed when the result is accepted, and a duplicate (or unknown) callID is dropped with a warning. Sent immediately when idle — the provider speaks the result as the turn's continuation (no explicit generation trigger exists or is needed) — otherwise queued behind the in-flight reply (e.g. a progress narration) so the trigger is never lost and the narration's kind tagging is not clobbered. The wire result slot expects a JSON-STRING, which is exactly the contract's outputJson shape — it passes through verbatim.

      Parameters

      • callID: string
      • outputJson: string

      Returns void

    • Streams one video frame as base64-encoded image data to the model. Optional capability — drivers that support inbound video tracks implement this.

      Parameters

      • base64Image: string

        Base64-encoded image data.

      • OptionalmimeType: string

        Image MIME type (defaults to 'image/jpeg').

      • OptionalsourceId: string

        Which 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.

      Returns boolean

      true if accepted and sent; false if dropped (throttled, unestablished, etc.).