npm package discovery and stats viewer.

Discover Tips

  • General search

    [free text search, go nuts!]

  • Package details

    pkg:[package-name]

  • User packages

    @[username]

Sponsor

Optimize Toolset

I’ve always been into building performant and accessible sites, but lately I’ve been taking it extremely seriously. So much so that I’ve been building a tool to help me optimize and monitor the sites that I build to make sure that I’m making an attempt to offer the best experience to those who visit them. If you’re into performant, accessible and SEO friendly sites, you might like it too! You can check it out at Optimize Toolset.

About

Hi, 👋, I’m Ryan Hefner  and I built this site for me, and you! The goal of this site was to provide an easy way for me to check the stats on my npm packages, both for prioritizing issues and updates, and to give me a little kick in the pants to keep up on stuff.

As I was building it, I realized that I was actually using the tool to build the tool, and figured I might as well put this out there and hopefully others will find it to be a fast and useful way to search and browse npm packages as I have.

If you’re interested in other things I’m working on, follow me on Twitter or check out the open source projects I’ve been publishing on GitHub.

I am also working on a Twitter bot for this site to tweet the most popular, newest, random packages from npm. Please follow that account now and it will start sending out packages soon–ish.

Open Software & Tools

This site wouldn’t be possible without the immense generosity and tireless efforts from the people who make contributions to the world and share their work via open source initiatives. Thank you 🙏

© 2026 – Pkg Stats / Ryan Hefner

@komaa/livekit-msteams-bridge

v0.3.0

Published

Bridge Microsoft Teams voice calls to a LiveKit Agent. Terminates the StandIn media-bridge wire protocol on one side and joins a LiveKit room (with agent dispatch) on the other.

Readme

Microsoft Teams Bridge for LiveKit Agents

CI npm version downloads docs MIT License TypeScript PRs Welcome

@komaa/livekit-msteams-bridge puts a LiveKit Agent on a real Microsoft Teams call - including avatar agents (bitHuman, Tavus, ...) whose voice the caller hears in Teams.

The hosted StandIn media bridge (standin.komaa.com) joins the Teams call and dials into this bridge over an HMAC-authenticated WebSocket. Per call, the bridge creates one LiveKit room, dispatches your agent into it (explicit dispatch by agentName), joins as a participant, publishes the caller's audio, and relays the agent's audio back to Teams.

Microsoft Teams call
       |
       v
StandIn media bridge       (hosted; joins the call)
       |   HMAC WebSocket, PCM 16 kHz
       v
this bridge                (you run it)
       |   WebRTC (room, one per call)
       v
LiveKit room  <--dispatch--  your LiveKit Agent
                             (STT + LLM + TTS + turn-taking, any plugin stack)

Both sides speak 16 kHz mono PCM16: the wire protocol natively, the room via the SDK's resampling AudioSource/AudioStream - the bridge itself never transcodes.

Features

  • Any LiveKit agent answers Teams calls - your existing agent (Python or Node, any STT/LLM/TTS/realtime plugin combo) needs no Teams-specific code. The bridge dispatches it by agentName with per-call metadata (caller name, tenant, direction, AAD id when known).
  • One room per call - clean lifecycle: room created at session.start, agent dispatched via the join token (RoomConfiguration), room deleted at teardown so the agent job ends immediately.
  • Turn-taking is the agent's own - VAD, interruption, and endpointing all run inside your LiveKit agent session, exactly as they do for WebRTC users.
  • Group-call awareness - participant counts and DTMF digits reach the agent as data messages on the teams.context topic.
  • Two call governors - a StandIn-side cutoff (the bridge forwards the goodbye request on teams.goodbye) and a bridge-side MAX_CALL_MINUTES hard cap.
  • Hardened transport (ported from the proven @komaa/elevenlabs-msteams-bridge): replay-proof single-use HMAC upgrade, connection caps checked before crypto, payload caps, backpressure bounds with control-frame exemption, pre-start timeout that only a real session.start clears, dead-peer detection (90 s), duplicate-call 409, pre-auth crash guard, graceful SIGTERM drain.
  • Observability - GET /healthz and GET /metrics (Prometheus text format): calls, durations, rejects, relay/drop counters.

Install

npx @komaa/livekit-msteams-bridge
# or
npm install @komaa/livekit-msteams-bridge

Node.js >= 20. Runtime deps: ws, livekit-server-sdk, @livekit/rtc-node (native).

Quick start

1. Prepare the agent

Any LiveKit agent works. Register it with an explicit agent name so the bridge can dispatch it:

# Python (agents >= 1.0): explicit dispatch by name
if __name__ == "__main__":
    cli.run_app(WorkerOptions(entrypoint_fnc=entrypoint, agent_name="my-teams-agent"))

Per-call metadata arrives in the job context (ctx.job.metadata, JSON): {"source":"msteams","caller_name":"...","tenant_id":"...","call_direction":"inbound","user_id":"<aad-id, when known>"}.

Optional: subscribe to the bridge's data topics - teams.context (participants/DTMF as {text}) and teams.goodbye ({text} the agent should speak before the call is cut).

Avatar agents (e.g. the bitHuman example): the caller hears the avatar's synchronized audio. The avatar's video stays in the room in v1 - the Teams tile is rendered by StandIn's own animated avatar (RMS lip-sync). Bridging room video to the Teams tile is on the roadmap.

2. Run the bridge

LIVEKIT_URL=wss://your-project.livekit.cloud \
LIVEKIT_API_KEY=API... \
LIVEKIT_API_SECRET=... \
LIVEKIT_AGENT_NAME=my-teams-agent \
WORKER_SHARED_SECRET=... \
  npx @komaa/livekit-msteams-bridge

Or as a library:

import { loadConfig, startServer } from "@komaa/livekit-msteams-bridge";
startServer(loadConfig()); // env-configured; see .env.example

3. Connect it to StandIn

Pick a tier at standin.komaa.com, pair an identity, then:

  1. Point the identity's agent WebSocket URL at this bridge (e.g. wss://lk-bridge.example.com:8080/voice/msteams/stream; StandIn appends /{callId} per call).
  2. Set WORKER_SHARED_SECRET to the pairing secret (both sides must match or the handshake is rejected with 401).
  3. Call your Teams bot. StandIn joins, dials the bridge, the bridge creates the room and dispatches your agent, and the agent answers.

Examples

  • examples/basic-bridge/ - embed the package in your own Node project (npm install @komaa/livekit-msteams-bridge, three lines of code).
  • examples/agents/ - two ready-made Python agents the bridge can dispatch: a minimal voice pipeline and a bitHuman avatar variant, both showing the three Teams integration points (agent_name, ctx.job.metadata, the teams.* data topics).

Configuration

See .env.example (ships with the package). Notable:

  • LIVEKIT_AGENT_NAME - explicit dispatch (LiveKit's recommended model). Unset = automatic dispatch (agent joins every room; prototype-only).
  • LIVEKIT_DELETE_ROOM_ON_END (default true) - delete the room at teardown so the agent job ends immediately instead of idling out (billing hygiene).
  • MAX_CALL_MINUTES / GOODBYE_TEXT / GOODBYE_GRACE_MS - the bridge-side governor. There is no bridge-side TTS on the room transport: the goodbye is a teams.goodbye data message your agent should speak, and the grace covers the unknown duration.
  • Transport hardening knobs: MAX_CONNECTIONS, MAX_CONNECTIONS_PER_IP (+ TRUST_PROXY_XFF behind a proxy), PRE_START_TIMEOUT_MS, WORKER_IDLE_TIMEOUT_MS, HMAC_FRESHNESS_MS.
  • TLS: the bridge serves plain WS - front it with a TLS terminator (tunnel / ingress / LB).

Known limitations (v1)

  • Barge-in flush: interruption handling runs inside the LiveKit agent (as designed), but the room emits no interruption event the bridge could map to the wire protocol's assistant.cancel - so up to ~1 s of already-relayed agent audio can play out after the caller cuts in. Acceptable in practice; an agent-published data event could close this later.
  • Video: caller video/screenshare frames are not published into the room, and room video (avatar agents) is not bridged to the Teams tile.
  • No deterministic goodbye: the governor's goodbye is spoken by the agent (teams.goodbye data topic), not synthesized by the bridge. The bridge flushes Teams-side playback first (assistant.cancel), but whether the agent interrupts its own in-flight sentence to speak the goodbye is the agent's choice - if its current turn outlasts GOODBYE_GRACE_MS, the goodbye gets cut. Have the teams.goodbye handler interrupt the current turn (see the example agents).
  • Reconnects: the LiveKit SDK retries transient drops internally (reconnecting/reconnected); Disconnected is final and ends the Teams call. There is no bridge-level room re-join beyond that.

Security notes

  • GET /healthz and GET /metrics are unauthenticated and served on the same port StandIn dials. Restrict the port at the network layer (or scrape through your ingress); the metrics expose only counters, never call content.
  • TRUST_PROXY_XFF takes the FIRST X-Forwarded-For hop, which is only trustworthy behind a single proxy that OVERWRITES the header (appending proxies make it client-controlled). Leave it off otherwise.
  • The default per-IP cap equals the global cap (no per-IP throttle) because legitimate traffic arrives from StandIn's small, fixed egress set - a small per-IP default would cap total concurrent calls. Set MAX_CONNECTIONS_PER_IP explicitly if your bridge is exposed more broadly.

Layout

src/
  server.ts      HTTP + WS upgrade, HMAC + replay guard, caps, drain (ported hardening)
  session.ts     per-call relay: worker WS ⇄ LiveKit room, governors, dead-peer
  livekit.ts     room join, agent dispatch (token RoomConfiguration), AudioSource/AudioStream
  protocol.ts    worker wire types (shared with the sibling bridges)
  hmac.ts        HMAC-SHA256("{timestampMs}.{callId}") sign/verify
  metrics.ts     Prometheus counters (/metrics)
  config.ts      env config (fail-loud numerics)
test/            node:test suites; a FakeRoom stands in for LiveKit (no network, no native module)