Skip to main content

Put a LiveKit Agent on Microsoft Teams voice/video calls. Terminates the StandIn media bridge wire protocol on one side and a LiveKit room on the other: per-call rooms, explicit agent dispatch, 16 kHz PCM relay, data-topic context, call governors.

Project description

livekit-msteams-bridge (Python)

Put a LiveKit Agent - including avatar agents - on Microsoft Teams voice/video calls.

PyPI package: livekit-msteams-bridge - the -py suffix is only in this repository's name, to distinguish it from the Node.js sibling repo.

This is the Python sibling of @komaa/livekit-msteams-bridge (Node.js) - same wire contract, same environment variables, drop-in interchangeable behind the same .env file. It terminates the StandIn media bridge wire protocol on one side and a LiveKit room on the other:

  • One room per call: the bridge creates a fresh room, joins as a participant, and dispatches your agent into it (explicit dispatch by agent_name - LiveKit's recommended model).
  • No transcoding on our side: the worker speaks 16 kHz mono PCM16 natively; the room side uses the SDK's resampling AudioSource/AudioStream, so the bridge itself never transcodes.
  • Agent integration without Teams code: per-call metadata (ctx.job.metadata - caller name, tenant, direction, AAD id when known) plus two data topics: teams.context (participants, DTMF, recording state) and teams.goodbye (the governor's goodbye line to speak).
  • Call governors: a bridge-side hard time cap (the agent speaks the goodbye), plus the StandIn-side governor.
  • Hardened: HMAC-signed upgrades with replay guard, connection caps, dead-peer detection, graceful SIGTERM drain, Prometheus /metrics.

StandIn is the hosted media bridge that joins the Teams call and dials this bridge - you run no Teams media stack yourself.

Documentation: komaa-com.github.io/livekit-msteams-bridge-py (getting started, example walkthrough, agents and dispatch, configuration and library reference, wire protocol). Teams/StandIn setup lives at docs.komaa.com.

Install

pip install livekit-msteams-bridge

Requires Python 3.10+.

Run

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

A .env file in the working directory is loaded automatically (existing environment wins). The bridge listens on ws://0.0.0.0:8080/voice/msteams/stream by default; StandIn appends /{callId} per call. Expose the port with a tunnel and register the wss:// URL as your identity's Agent voice URL in the StandIn dashboard.

LIVEKIT_AGENT_NAME must equal the agent_name your worker registers with (WorkerOptions(entrypoint_fnc=..., agent_name="teams-voice-agent")). A mismatch is the classic silent failure: the room is created, the caller hears nothing, and the worker never gets a job.

Embed

import asyncio
from livekit_msteams_bridge import load_config, start_server

async def main():
    server = await start_server(load_config())
    await asyncio.Event().wait()  # run until cancelled

asyncio.run(main())

Tests can inject a fake room with the connect_room argument - see the test suite's FakeRoomPort for the shape.

Configuration

Everything is environment variables; names are identical to the Node package.

Variable Required Default Meaning
WORKER_SHARED_SECRET yes - Must equal the shared secret from StandIn pairing (HMAC upgrade check).
LIVEKIT_URL yes - LiveKit server URL (wss://<project>.livekit.cloud or self-hosted).
LIVEKIT_API_KEY / LIVEKIT_API_SECRET yes - Mint join tokens, dispatch agents, delete rooms. Server-side only.
LIVEKIT_AGENT_NAME no - Named agent for explicit dispatch (recommended). Unset = automatic dispatch (prototype-only).
LIVEKIT_ROOM_PREFIX no msteams- Room name prefix; the room is {prefix}{callId} (sanitized).
LIVEKIT_DELETE_ROOM_ON_END no true Delete the room at teardown so the agent job ends immediately (billing hygiene).
MAX_CALL_MINUTES no 0 (off) Bridge-side hard cap per call; on expiry the agent is asked to say goodbye, then the call ends.
GOODBYE_TEXT / GOODBYE_GRACE_MS no (default line) / 8000 Goodbye wording (sent on teams.goodbye) and playout grace. The call ends GOODBYE_GRACE_MS + a fixed 500 ms scheduling buffer after the goodbye request.
PORT / BIND no 8080 / 0.0.0.0 Listen port / bind address.
HMAC_FRESHNESS_MS no 60000 Two-sided freshness window: a timestamp up to 60 s in the past OR the future is accepted, and the replay guard holds a used handshake until the timestamp ages out.
MAX_CONNECTIONS / MAX_CONNECTIONS_PER_IP no 64 / = total Connection caps.
PRE_START_TIMEOUT_MS no 10000 Drop a worker that authenticates but never sends session.start.
WORKER_IDLE_TIMEOUT_MS no 90000 Dead-peer window (the worker heartbeats every 30 s).
TRUST_PROXY_XFF no false Trust the first X-Forwarded-For hop for the per-IP cap.
TLS_CERT_PATH / TLS_KEY_PATH no - Serve native TLS (wss). Otherwise front the plain WS with a TLS terminator.
LOG_LEVEL no info debug / info / warn / error.

Endpoints

  • GET /healthz - liveness.
  • GET /metrics - Prometheus counters (calls, rejections, relayed/dropped frames).
  • GET /{...}/{callId} + WebSocket upgrade - the worker wire, HMAC-signed with X-OpenClawTeamsBridge-Timestamp / X-OpenClawTeamsBridge-Signature over "{timestampMs}.{callId}".

Notes for operators:

  • /healthz and /metrics are unauthenticated (only the WebSocket upgrade is HMAC-gated). They expose no call content - just liveness and counters - but if you would rather not leak call volumes, keep the port behind your ingress/tunnel rules.
  • Barge-in: interruption handling lives inside your LiveKit agent session (VAD, turn-taking), exactly as for WebRTC callers; the room transport gives the bridge no interruption event to relay, so the worker's own flush-on-silence smooths the tail end.
  • The bridge participant's join token has a fixed 6 h TTL. Calls that should be allowed to run longer than that need a re-join strategy; in practice set MAX_CALL_MINUTES well below it.
  • Inbound Teams video (video.frame) is not forwarded to the room in this version - the bot's tile is rendered by the worker's own avatar. Publishing caller video as a room track is on the roadmap.
  • The Docker image exposes port 8080 and does not remap PORT/BIND at the Docker layer; use -e PORT=... -p <host>:<port> together if you change them.

Agent integration points

Your agent needs no Teams-specific code, but three integration points are available:

  • agent_name in WorkerOptions - must match LIVEKIT_AGENT_NAME for explicit dispatch.
  • ctx.job.metadata (JSON) - per-call context: source, caller_name, tenant_id, call_direction, and user_id (AAD id when Teams provides one).
  • Data topics - teams.context (participant count, DTMF, recording state) and teams.goodbye (the governor's goodbye line; have your handler speak it and interrupt the current turn).

License

MIT (c) Alaaeldin Elhenawy

Project details


Download files

Download the file for your platform. If you're not sure which to choose, learn more about installing packages.

Source Distribution

livekit_msteams_bridge-0.2.0.tar.gz (42.2 kB view details)

Uploaded Source

Built Distribution

If you're not sure about the file name format, learn more about wheel file names.

livekit_msteams_bridge-0.2.0-py3-none-any.whl (31.5 kB view details)

Uploaded Python 3

File details

Details for the file livekit_msteams_bridge-0.2.0.tar.gz.

File metadata

  • Download URL: livekit_msteams_bridge-0.2.0.tar.gz
  • Upload date:
  • Size: 42.2 kB
  • Tags: Source
  • Uploaded using Trusted Publishing? Yes
  • Uploaded via: twine/6.1.0 CPython/3.13.12

File hashes

Hashes for livekit_msteams_bridge-0.2.0.tar.gz
Algorithm Hash digest
SHA256 1bedc2a1999d4e291cdc5ce53ed67a75927f25c086a7b99b68474e487e40b542
MD5 433ff84ba926ca9133492d32cc42ee74
BLAKE2b-256 1417a5ac1c2c226480ea538b1073f0415cf84ef437ca48e4e4d2cf84b7e6338c

See more details on using hashes here.

Provenance

The following attestation bundles were made for livekit_msteams_bridge-0.2.0.tar.gz:

Publisher: publish.yml on komaa-com/livekit-msteams-bridge-py

Attestations: Values shown here reflect the state when the release was signed and may no longer be current.

File details

Details for the file livekit_msteams_bridge-0.2.0-py3-none-any.whl.

File metadata

File hashes

Hashes for livekit_msteams_bridge-0.2.0-py3-none-any.whl
Algorithm Hash digest
SHA256 5def7723a1aaa5e4ba2b3f73de91d6e991efab5da59146ce5c9a671d2eab6ef4
MD5 ca9057c5a44b94f3a42e81cd9fd6ae3a
BLAKE2b-256 2176548b827ddb5aac5fbadb1bde9f91c45184aabc3c9a1541525533b4227ce8

See more details on using hashes here.

Provenance

The following attestation bundles were made for livekit_msteams_bridge-0.2.0-py3-none-any.whl:

Publisher: publish.yml on komaa-com/livekit-msteams-bridge-py

Attestations: Values shown here reflect the state when the release was signed and may no longer be current.

Supported by

AWS Cloud computing and Security Sponsor Datadog Monitoring Depot Continuous Integration Fastly CDN Google Download Analytics Pingdom Monitoring Sentry Error logging StatusPage Status page