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

    Accessors

    • 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 IsBusy(): boolean

      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.

      Returns boolean

    Methods

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

    • Tears down the socket, mic capture, mic tracks, and playout engine, resets the response state machine, and emits a final 'closed' (unless already 'error'). Sends session.end first — without it the provider holds the session (billable) for a 30-second resume window. Safe to call more than once.

      Returns Promise<void>

    • Returns the AGENT's remote-audio MediaStream when this driver owns a tappable remote-audio plane (e.g. a WebRTC peer-connection driver routes the model's audio track here), or null otherwise. Hosts use it to MIX the agent's voice into a browser-side recording alongside the mic; a null return (the default, and what every non-WebRTC driver gives) degrades gracefully to mic-only capture.

      Optional capability: the method itself is optional — call sites must use client.GetRemoteMediaStream?.() ?? null. Drivers that can't expose a remote stream simply don't implement it (or return null).

      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

    • Optional capability: registers a handler invoked when the agent's remote-audio stream becomes available — immediately if it has already landed, otherwise when the WebRTC track arrives (typically AFTER Connect resolves). Lets a host attach the agent voice to a recording that began before the track landed. Call as client.OnRemoteMediaStream?.(cb); drivers without a remote stream simply don't implement it.

      Parameters

      • handler: (stream: MediaStream) => void

      Returns void

    • Registers the (single) remote-VIDEO handler — the model/avatar's video track for a VIDEO session (a talking-head the host renders, e.g. as the agent's tile). Invoked once the provider publishes its video track.

      Optional capability: audio-only drivers (the default) never emit — registering is always safe, but hosts must not assume a video track arrives. Video-capable drivers (BaseRealtimeModel.SupportsVideo) call emitRemoteVideo when the track is live.

      Parameters

      • handler: (stream: MediaStream) => void

        Invoked with the remote video MediaStream 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 background context — EMULATED via the mutable system_prompt: the note is appended under a "Background updates" heading and the FULL prompt is re-sent via session.update. A config write never triggers or disturbs generation, so it is sent immediately even while a reply is in flight (no queueing applies).

      Parameters

      • text: 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

    • Mutes / unmutes by toggling the mic tracks' enabled flag: the capture pipeline stays up and streams SILENCE while muted (the provider's VAD sees a continuous stream and the un-mute is glitch-free — same policy as the other client drivers).

      Parameters

      • muted: boolean

      Returns void