@komaa/standin-sdk is one end of a socket. StandIn, the hosted service, joins the Microsoft Teams
call and owns the Microsoft side entirely: the bot registration, Graph, media negotiation, the avatar
tile. It then dials your worker, once per call, over an authenticated WebSocket. This package answers
that dial, speaks the call protocol, and hands each call to your code.
import { CallServer } from "@komaa/standin-sdk" works with no framework installed. Each
plugin is a subpath of that same package: @komaa/standin-sdk/openclaw,
@komaa/standin-sdk/echo.
What you actually write
The whole contract between the SDK and your agent is seven optional methods and a session object. Nothing extends anything, and you implement only the ones you care about.Who owns what
CallServer owns everything that is the same whichever agent framework is on the other side. That
list is not marketing, it is the reason the SDK exists:
- the socket StandIn dials, and the HMAC handshake with its single-use replay guard
- capacity, draining, and the one-live-session-per-call rule
- the wire protocol and the frame loop
- outbound sequence numbers and the audio timeline, for voice and for the video tile alike
- five timers: a pre-start watchdog, a caller-audio idle
watchdog, an
onStarttimeout, a stale-call reaper and an optional call-duration ceiling - idempotent teardown that always frees the slot
The two SDKs are one API
The wire protocol, the audio format and the HMAC are byte-identical between the languages, and shared conformance vectors prove it rather than promising it: both protocol modules are generated from one schema and gated against drift. The core seam is the same in both. Same handler methods, same session, same watchdogs, same defaults. A handler ported between the languages changes only its method names.- Timers are milliseconds in TypeScript and seconds in Python.
preStartTimeoutMs: 10000againstpre_start_timeout=10.0. The names differ too, so the compiler catches a straight port, but a config file copied between two workers does not. - The two optional video callbacks sit differently. Every
CallHandlermember is optional in TypeScript, soonVideoFrameandonSpeakerChangeare simply two more of them. Python keeps them on separateVideoHandlerandSpeakerHandlerprotocols, becauseCallHandlerthere isruntime_checkableand a sixth member would breakisinstancefor every handler written before the vision lane existed. - Who may call the agent is a TypeScript module.
isInboundCallAllowedand its three companions ship in this SDK only. A Python worker writes the same check in its own handler. See Security.
Why the plugin lists differ per language
Most plugins exist in both languages, and the ones that do not are settled by the same rule: which language a plugin lives in is decided by the framework it integrates, not by preference. ElevenLabs, Deepgram, Cartesia and LiveKit are in both trees. OpenClaw has to be TypeScript because it loads inside the OpenClaw gateway process and consumes in-process objects there. Hermes Agent is Python for the same reason, in reverse. OpenAI Realtime is a TypeScript plugin. Nothing is missing from either side of the wire: both SDKs speak the same protocol, run the same conformance vectors, and answer the same calls.What is in the package
Directory names are short because the repository is already called
standin. The published name
carries the scope because a registry is global. They are allowed to differ.
Requirements
- Node.js 20 or newer for the core. The OpenClaw plugin follows its host and wants 22.19 or
newer, a requirement that travels with the
openclawpeer dependency rather than with the core. - A StandIn identity and its connection secret, from standin.komaa.com
- A public
wss://route to your worker, so StandIn can dial in
The names you reach for first
Every name below resolves from the bare specifier. This is the handful a first worker needs, not the whole surface:The whole surface, by lane
The barrel re-exports thirty-one modules. Nothing here is a subpath import: a plugin subpath is only ever a framework adapter, and every name below comes from@komaa/standin-sdk itself.
Where the SDK stops
Three boundaries, and each is a real line rather than a disclaimer. Speech and reasoning are your framework’s. Speech recognition, speech generation and the model that decides what to say belong to your provider. The SDK carries PCM in both directions and gives you the turn-taking pieces if you are assembling three vendors yourself, but nothing in this package transcribes, synthesizes or thinks. The Microsoft side is StandIn’s. Bot registration, Graph, the media negotiation and the call itself. Your worker never holds a Bot Framework credential and never talks to Microsoft. StandIn draws the avatar tile; the SDK sends it hints. This is the boundary that is most often described wrongly, so it is worth stating exactly. The SDK does carry the vision and avatar lane:session.express names an emotion, session.sendSpeechMarks sends the viseme timeline that drives
lip-sync, session.displayImage puts a picture on the tile, session.sendTileFrame puts one frame of
your own video on it, and session.latestVideoFrame and CallHandler.onVideoFrame are how the
caller’s camera and screen share reach you. Whole modules back those: avatar, lipsync, vision,
visionTools, tile and ambient, all on the barrel above. What the SDK does not do is render
anything. It never composites a face, never encodes the avatar and never knows how the tile is drawn.
See Vision and the avatar and The avatar.
A frame type this SDK does not recognise is ignored by contract, which is what lets an older worker
and a newer StandIn interoperate. That rule is about unknown frames, not about the avatar: those are
frames the SDK itself sends and parses.
Next
Quickstart
From one install to a Microsoft Teams call that answers.
Call handler
The seven methods and the session object.
Audio
Rates, frames, and the clipping trap.
Security
The two HMAC lanes and their replay windows.