Skip to content

Configuration Reference

The bridge is configured entirely from environment variables - the same names as the Node.js sibling, so one .env file drives either implementation. The package ships a fully commented .env.example. Four variables are required.

EnvMeaning
BRIDGE_SECRETThe shared secret from StandIn pairing. Must equal what StandIn holds, or the HMAC upgrade is rejected with 401.
LIVEKIT_URLLiveKit server URL (wss://<project>.livekit.cloud or self-hosted).
LIVEKIT_API_KEYLiveKit API key; mints join tokens, dispatches agents, deletes rooms. Server-side only.
LIVEKIT_API_SECRETLiveKit API secret paired with the key.
EnvDefaultMeaning
LIVEKIT_AGENT_NAMEunsetThe agent_name your worker registers with, for explicit dispatch (recommended). Unset falls back to automatic dispatch (an unnamed agent joins every room; prototype-only).
LIVEKIT_ROOM_PREFIXmsteams-Room name prefix; the room is {prefix}{callId} (sanitized, capped at 100 chars to match the Node bridge).
LIVEKIT_DELETE_ROOM_ON_ENDtrueDelete the room at teardown so the agent job ends immediately instead of idling out (billing hygiene).
EnvDefaultMeaning
MAX_CALL_MINUTES0 (off)Bridge-side hard cap per call, in minutes (fractional allowed).
GOODBYE_TEXTa default lineThe goodbye line sent to the agent on msteams.goodbye.
GOODBYE_GRACE_MS8000How long the agent gets to speak the goodbye before session.end. The call ends this grace + a fixed 500 ms scheduling buffer after the request.

Continuous visual awareness of the call: the newest frame of each participant’s screen-share and camera is published to the agent on the msteams.vision byte stream, attributed with whose screen it is. The agent uses it as context on its next natural turn - it never triggers speech.

EnvDefaultMeaning
AMBIENT_VISIONfalseMaster switch. Off by default: this is the knob that costs money per frame, so a bridge nobody configured never starts spending because a worker happened to send video.
MAX_VISION_PER_MINUTE30Per-call spend cap over a sliding 60 s window. 0 DISABLES rather than meaning unlimited: for a high cap, set a high number.
REQUIRE_RECORDING_STATUStrueHold frames until Teams reports the call recording as active (Media Access obligation). Frames are not even stored before then, so nothing captured beforehand can surface later.

Only CHANGED frames are sent (a frozen screen costs nothing), screen-share is tried before camera so a tight budget spends its last slot on the screen, and a failed publish refunds the slot and leaves the frame retryable. Booleans here are strict: REQUIRE_RECORDING_STATUS=yes stops startup rather than quietly disabling the gate.

EnvDefaultMeaning
PORT9442TCP port the bridge listens on (the StandIn calling-lane port, shared by every bridge plugin).
BIND0.0.0.0Bind address.
WS_PATH/msteams/callingBase path the worker WebSocket is anchored on; a call dials {WS_PATH}/{callId} and nothing else is accepted (a foreign path is a 401, before authentication). This is what an identity registered with a bare host will reach. /healthz and /metrics stay at the root. An empty value stops startup.
TLS_CERT_PATH / TLS_KEY_PATHunsetPEM cert/key for native TLS (wss). When both are set the bridge serves TLS itself; otherwise front the plain WS with a TLS terminator.
HMAC_FRESHNESS_MS60000Two-sided freshness window: a timestamp up to 60 s in the past OR the future is accepted; the replay guard holds a used handshake until the timestamp ages out.
MAX_CONNECTIONS0 (= 64)Max concurrent connections.
MAX_CONNECTIONS_PER_IP0 (= total cap)Per-IP cap. Defaults to the total cap because StandIn dials from a small set of egress IPs.
TRUST_PROXY_XFFfalseTrust the first X-Forwarded-For hop for the per-IP cap. Only enable behind a proxy you control.
PRE_START_TIMEOUT_MS0 (= 10000)Drop a connection that authenticates but never sends session.start.
WORKER_IDLE_TIMEOUT_MS0 (= 90000)Dead-peer window: end the call after this long without any worker message (the worker heartbeats every 30 s). Frees the call id for reconnect and ends the agent job.
LOG_LEVELinfodebug | info | warn | error. An invalid value falls back to info.

The bridge also exposes GET /metrics (Prometheus text format, no auth): calls total/active, call seconds, upgrade rejections by cause, frames relayed each way, ambient-vision images delivered, backpressure drops, room connect failures, governor fires, goodbye requests, unparseable frames, and callid mismatches. Like GET /healthz it is served on the same port - keep the port private to your network or scrape through your ingress.

The bridge participant’s join token has a fixed 6 h TTL; set MAX_CALL_MINUTES well below that for calls that must end cleanly.