Member Junction
    Preparing search index...

    Module @memberjunction/ai-bridge-ringcentral

    @memberjunction/ai-bridge-ringcentral

    The RingCentral Realtime Bridge driver — a telephony bridge in MemberJunction's Realtime Bridges program. It connects the one realtime agent engine to a phone call over a registered SIP softphone (ringcentral-softphone over SIP/TLS + RTP/SRTP): full-duplex audio in/out, outbound dial + inbound INVITE answer, DTMF send/receive, and blind transfer — all behind an injectable telephony call SDK seam so the driver builds and unit-tests with no network and no real SIP client.

    RingCentral's only product that carries bidirectional call audio is a registered SIP softphone. RingCentral's WebSocket "Call Streaming" product is receive-only — it lets you hear a call but has no way to inject the agent's voice — and its Call Control REST API is signaling-only (no media plane). A realtime voice agent needs duplex audio, so this driver registers as a SIP device and carries RTP both ways. (Confirmed by RingCentral: "we don't have such a voice API to stream bi-direction audio" — that's Call Streaming; duplex is SIP-only.)

    A phone call goes through the same transport seam as every meeting bridge — it is just a single-leg audio media source. Telephony is audio only: no video, no screen, and no Meeting Controls / facilitator surface (a 1:1 call has no roster to facilitate).

    See the Realtime Bridges Guide (§ Telephony bridges) and the program DEPLOYMENT.md §6b for the end-to-end RingCentral setup (the five SIP credentials + DID routing).

    npm install @memberjunction/ai-bridge-ringcentral
    

    ringcentral-softphone and its werift-rtp peer are optional dependencies loaded lazily only when the RingCentral provider is configured (the package builds + unit-tests with neither installed).

    • RingCentralBridge@RegisterClass(BaseRealtimeBridge, 'RingCentralBridge'). The MJ: AI Bridge Providers row with DriverClass = 'RingCentralBridge' resolves to this driver via the ClassFactory. A thin subclass of BaseTelephonyBridge — all call lifecycle, the audio media seam, DTMF, transfer, the caller+agent roster, and capability gating are inherited.
    • RingCentralSoftphoneHandle + createRingCentralSoftphone — the process-wide, long-lived SIP registration (constructed + registered once at boot). It owns the inbound-INVITE stream: it parks each INVITE by SIP Call-ID and notifies a listener with the parsed caller/DID, so the bridge that the server then starts can answer it. Per-call SDKs draw their session from it (SoftphoneCallSource).
    • RingCentralSoftphoneCallSdk — the full-duplex binding of the platform-agnostic ITelephonyCallSdk seam over one softphone call session.
    • RealtimeRtpSender — the outbound audio clock: an appendable, self-paced 20 ms RTP sender (the SDK's own Streamer only plays a fixed pre-recorded buffer and can't be fed a live stream). Barge-in drops its queue so the agent goes silent immediately.
    • RingCentralCallSdk / RingCentralCallSdkFactory — the unbound default SDK the bridge falls back to (every op throws "bind the real RingCentral client"); the server overrides it with the softphone SDK at StartBridgeSession via BindSdk, so a misconfigured deployment fails loudly rather than silently.
    Capability Status
    Outbound dial
    Inbound routing
    Audio in / out ✅ (full duplex over RTP/SRTP)
    DTMF send / receive
    Call transfer ✅ (blind / SIP REFER)
    Video / screen ➖ n/a — telephony is audio only (SendMedia no-ops video/screen out)
    Meeting Controls / facilitator ➖ n/a — a 1:1 call has no roster (GetMeetingControlsEventSourcenull)
    Recording ➖ not enabled on the seed row — StartRecording stays capability-gated and throws

    Capability gating is two-layer (defense-in-depth): the engine checks the provider's SupportedFeatures first, and the base re-asserts each flag with RequireFeature at the top of its overrides.

    The shipped RealtimeBridgeContext is a meeting-shaped contract with no Direction field, so the server forwards the session's direction (and the inbound call id + carrier rate) into the driver's Configuration:

    Config key Purpose
    Direction 'Outbound' (default) → sdk.dial(to, from); 'Inbound'sdk.answer(callId)
    FromNumber the agent's DID (informational — the softphone's caller-id is fixed by its SIP registration)
    InboundCallId the SIP Call-ID of the inbound call to answer (the handle parked the INVITE under it)
    CarrierSampleRate the SIP leg's PCM16 rate — 16000 for OPUS/16000 — so the base bridge resamples to/from the model rate without bottlenecking through 8 kHz

    Connect requires AudioIn + AudioOut; outbound additionally requires OutboundDial, inbound requires InboundRouting.

    RingCentralSoftphoneCallSdk maps the telephony seam onto the ringcentral-softphone SDK (codec OPUS/16000 → clean PCM16 mono 16 kHz both directions):

    ITelephonyCallSdk op softphone binding
    dial (outbound) softphone.call(to)OutboundCallSession (caller-id is the registration's; from is informational)
    answer (inbound) softphone.answer(parkedInvite)InboundCallSession, resolved from the handle by SIP Call-ID
    hangup session.hangup() (SIP BYE) + stop the RTP sender
    sendAudioFrame enqueue on RealtimeRtpSender → paced 20 ms RTP frames (encoder.encodeRtpPacketsession.sendPacket)
    onAudioFrame session.on('audioPacket', …) → decoded PCM16 (the caller's voice)
    sendDtmf session.sendDTMF(char) per digit
    onDtmf session.on('dtmf', …)
    transfer session.transfer(to) (blind REFER)
    onCallEnded session.once('disposed', …) (either party hangs up); outbound also 'busy'
    flushOutbound drop the RTP sender's queue (barge-in — the softphone holds no outbound buffer to flush)

    The five SIP device credentials (domain / outboundProxy / username / password / authorizationId) resolve upstream from the deployment config (an "Existing Phone" BYOD device — see DEPLOYMENT.md §6b) — never inline secrets, and none of the ringcentral-softphone / werift-rtp types leak into this package (they're loaded lazily behind structural seams).

    The bridge is not used directly — the server's RingCentralTelephonyService creates + registers the shared softphone at boot, routes inbound INVITEs to AIBridgeEngine.StartBridgeSession, and binds the softphone SDK onto the driver. There is no channel host to wire for telephony (no Meeting Controls).

    The suite is fully offline (no SIP client, no werift-rtp, no network):

    • realtime-rtp-sender.test.ts — 20 ms framing, RTP counter advancement + sequence wrap, sub-frame remainder carry, barge-in flush, the disposed-session guard.
    • ringcentral-softphone-call-sdk.test.ts — dial/answer wiring, inbound audio → onAudioFrame, paced outbound audio (driven with fake timers through the real sender), barge-in, DTMF send/receive, transfer, hangup, disposed/busyonCallEnded.
    • ringcentral-softphone-handle.test.ts — inbound INVITE parking + onInvite notification, answer / decline by call id, and the pure SIP-header parsing (parseInvite / getHeader / extractSipNumber).
    • ringcentral-bridge.test.ts — the driver + the unbound-RingCentralCallSdk bind-me throw + capability gating (video/screen/Meeting Controls correctly absent).
    cd packages/AI/RealtimeBridge/Providers/RingCentral && npm run test
    

    Classes

    RealtimeRtpSender
    RingCentralBridge
    RingCentralCallSdk
    RingCentralSoftphoneCallSdk
    RingCentralSoftphoneHandle

    Interfaces

    InboundInviteInfo
    IRingCentralClientBindings
    RingCentralSoftphoneConfig
    RtpConstructors
    RtpHeaderInit
    RtpPacketInstance
    SoftphoneCallSession
    SoftphoneCallSource
    SoftphoneClient
    SoftphoneCodec
    SoftphoneInviteMessage

    Type Aliases

    RtpHeaderInstance

    Variables

    RINGCENTRAL_BRIDGE_DRIVER_CLASS
    RTP_FRAME_MS

    Functions

    bufferToArrayBuffer
    createRingCentralSoftphone
    extractSipNumber
    getHeader
    LoadRingCentralBridge
    parseInvite
    RingCentralCallSdkFactory