Skip to main content
@komaa/standin-sdk/deepgram puts Deepgram on a real Microsoft Teams call. StandIn answers the call and dials your worker; this plugin answers that dial, opens one session per call, and relays the audio both ways.

Install and run

There is nothing to install beyond the SDK. Deepgram is reached over an ordinary WebSocket, so this plugin adds no dependency.
Expose port 9442 at the /msteams/calling path and register the public wss:// URL as your StandIn identity’s agent voice URL. The full walkthrough is in the Quickstart, and there is a runnable example at examples/deepgram-msteams-connector.

The audio format

Audio is pinned to linear16 at 16 kHz in both directions, which is exactly what a Microsoft Teams call carries, so the hot path is a copy.

What the agent can do about the call

Declared automatically when the call starts, so there is nothing to set up on the provider’s side. show_image fetches the URL through the SDK’s guard: public hosts only, no private or link-local addresses, and the address is re-checked at connect time. display is fullscreen or overlay, and this plugin routes the call through CallTools.dispatch, so the model’s choice is honoured. See Vision and the avatar for what each placement is for, and Sending a picture for putting a picture into chat instead. A Voice Agent hears but does not see, so look needs a vision model of your choosing. Set STANDIN_VISION_API_URL and STANDIN_VISION_MODEL to any OpenAI-compatible endpoint that accepts images, including one you run yourself. The frame is sent for inference and not stored, and only the description comes back. Without it the agent is told plainly that looking is unavailable, rather than being left with silence. The whole surface is on Vision and the avatar. Pass a describer in the handler options to point this plugin at a different vision model from the one the environment names, which is how one worker answers two identities with two vision budgets. Build it with FrameDescriber.fromEnv() or its constructor, both on Vision and the avatar.

How context reaches the agent

The Voice Agent API has no non-interrupting context message, so this plugin folds participant counts, key presses and recording changes into the agent’s prompt and pushes it with an update. That has a cost worth knowing: the prompt is resent in full on every change, so the context section is a rolling window of the last eight notes rather than a transcript. A meeting where people join and leave repeatedly keeps the recent notes and drops the old ones, which is the right trade when the alternative is a prompt that grows for the length of the call. Context that arrives before the agent socket is open is held rather than dropped, then folded in once it exists. The “there are N people here, stay quiet” line and the recording change both land in exactly that gap.

Tools of your own

A custom tool runs in your worker, so it can reach whatever your worker can reach:
The description is what the model reads to decide whether to call it, so write it for a model rather than for a developer. Keep the handler fast: the caller is waiting in silence while it runs. A custom tool may not shadow a built-in one. That is refused when the handler is built rather than at the first call, because a shadowed end_call is an agent that has quietly lost the ability to hang up.

Inside your own worker

The plugin is a CallHandler like any other:
Read the configuration once, then close over it. handlerFactory runs once per call, so new DeepgramHandler() with nothing passed reads the environment again on every call, and a key removed after startup would fail the next caller rather than failing you. serve(), which is what npx standin-deepgram runs, does exactly this, then waits for SIGINT or SIGTERM before calling server.aclose(). server.start() returns as soon as the listener is bound, so a script that ends there exits before a single call arrives, and aclose() is what drains live calls and releases the port.

Configuration

The think-endpoint pair is the reason most people pick this plugin: point DEEPGRAM_THINK_ENDPOINT_URL at a model of your own and Deepgram keeps the listening and the speaking while your model does the reasoning.
The configuration is read once when the worker starts, not per call, so a missing key stops the worker at startup rather than surprising the first caller. Both hosts are checked against the deepgram.com suffix, because your API key travels to them: a wrong host would be credential leakage rather than a failed call. The reasoning behind that shape is on Configuration.

Next

Realtime providers

The startup buffer, the echo guard and barge-in this plugin sits on.

Vision and the avatar

What look, show_image and express reach.