Skip to main content
@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.
One package. The core is the bare specifier and imports nothing from the plugins, so 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.
That is a worker that answers a real Microsoft Teams call. Everything else on this page is detail about what the server is doing behind those two methods.

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 onStart timeout, a stale-call reaper and an optional call-duration ceiling
  • idempotent teardown that always frees the slot
Your handler owns the part that differs: what to do with a caller’s voice, and where the reply comes from. If you find yourself reimplementing a sequence number or a resampler, that is a bug in the SDK, not in your plugin.

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.
Python is snake_case, TypeScript is camelCase. Around the seam a few peripheral helpers exist in one language only, and the page each one belongs to says so where it applies. Three that matter here:
  • Timers are milliseconds in TypeScript and seconds in Python. preStartTimeoutMs: 10000 against pre_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 CallHandler member is optional in TypeScript, so onVideoFrame and onSpeakerChange are simply two more of them. Python keeps them on separate VideoHandler and SpeakerHandler protocols, because CallHandler there is runtime_checkable and a sixth member would break isinstance for every handler written before the vision lane existed.
  • Who may call the agent is a TypeScript module. isInboundCallAllowed and 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 openclaw peer 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.