Library API
Most deployments run the CLI (npx @komaa/livekit-msteams-bridge). When you want to embed the bridge in a larger Node process - to share a supervisor, add your own health checks, or inject a test double - import it instead. The package is ESM, TypeScript-typed, and Node >= 20.
Typical embedding
Section titled “Typical embedding”import { loadConfig, startServer } from "@komaa/livekit-msteams-bridge";
const server = startServer(loadConfig());server.on("error", (err) => { console.error(`bridge server error: ${err.message}`); process.exit(1);});startServer returns the Node http.Server, so you own its lifecycle (close(), listening, etc.). SIGTERM/SIGINT draining of live calls is wired automatically.
Exports
Section titled “Exports”Config
Section titled “Config”loadConfig(): BridgeConfig- read and validate all environment variables. Throws on a missing required var or a bad numeric.BridgeConfig(type) - the resolved config shape.
Server
Section titled “Server”startServer(cfg, connectRoom?): http.Server- start the worker-facing server.connectRoomis an injectableRoomConnector(defaults to the real LiveKit implementation); pass a fake in tests to avoid the native module and the network.authorizeUpgrade(cfg, req, replay?)- the HMAC + replay check for a WS upgrade; returns{ callId }or{ error }.callIdFromUrl(url)- extract thecallIdfrom an upgrade URL (returnsnullon a malformed escape).ReplayGuard- single-use guard for verified upgrade tuples.
Session and LiveKit
Section titled “Session and LiveKit”CallSession- one call: pairs the worker WebSocket with anAgentRoomPortand relays audio.AgentRoomPort,RoomHandlers,RoomConnector(types) - the interface between the session and the room, so you can substitute your own room implementation.connectLiveKitRoom(cfg, log, callId, metadata, handlers)- the real LiveKit connector (join, dispatch, publish/subscribe).TOPIC_CONTEXT("msteams.context"),TOPIC_GOODBYE("msteams.goodbye"),TOPIC_VISION("msteams.vision") - the topics the agent listens on.TOPIC_TRANSCRIPTION("lk.transcription") is LiveKit’s own topic, which the bridge reads for the group-call gate.
Group-call gate
Section titled “Group-call gate”Pure policy, no I/O - safe to reuse anywhere.
resolveGroupCallGateConfig(partial),GROUP_CALL_GATE_DEFAULTS- the single place defaults are applied.isAddressed(transcript, wakePhrases)- case-insensitive, boundary-aware wake-phrase match.isFollowUpWindowOpen({ lastAddressedAt, followUpWindowMs, now })- the follow-up window (a time window, never a latch).isGroupGateActive(config, isGroup),hasUsableWakePhrase(phrases),groupCallEtiquetteClause(config).
Ambient vision
Section titled “Ambient vision”AmbientVision- the per-call frame store, change latch, spend budget and fallback queue. Constructed withmediaPermitted/sinkReady/delivercallbacks, so it is testable without a room.VisionBudget- the sliding 60-second per-call cap (tryConsume/refund/release), clock injected.resolveAmbientVisionConfig(partial),AMBIENT_VISION_DEFAULTS,describeFrameOwner(frame),visionSourceOf(raw),VisionImage(type).
Call lifecycle
Section titled “Call lifecycle”CallReaper- polls a live-call registry and ends calls whose agent never answered.reapStale()is public, so it can be driven directly in a test.isUnanswered(call, staleCallReaperMs, now)- the whole decision as a pure predicate.ReapableCall(type) - what the reaper needs from a call (startedAtMs,answeredAtMs,shutdown).
sign(secret, timestampMs, callId),verify(secret, timestampMs, callId, signature),isFresh(timestampMs, windowMs)- the handshake primitives.TIMESTAMP_HEADER,SIGNATURE_HEADER- the header names carrying the handshake.
Protocol and metrics
Section titled “Protocol and metrics”- Everything from the wire protocol module (message types,
parseWorkerMessage,pcm16kBytesToMs) - see Wire Protocol. renderMetrics(): string- the Prometheus exposition text served atGET /metrics.logger(scope),Logger(type) - the structured logger the bridge uses.
Testing against a fake room
Section titled “Testing against a fake room”startServer’s second argument lets tests drive a full call without LiveKit:
import { startServer, type RoomConnector } from "@komaa/livekit-msteams-bridge";
const fakeConnector: RoomConnector = async (_cfg, _log, callId, _meta, handlers) => ({ roomName: `fake-${callId}`, async publishCallerAudio() {}, sendContext() {}, sendGoodbye() {}, async close() {},});
const server = startServer(cfg, fakeConnector);This is exactly how the package’s own node:test suites exercise the session and transport without the network or the native @livekit/rtc-node module.