Skip to content

Library API

The package is both a CLI and an importable TypeScript library. Everything below is exported from the package root and fully typed.

import { loadConfig, startServer } from "@komaa/openai-msteams-bridge";

loadConfig() reads the same environment variables as the CLI and throws a clear error when a required variable is missing or a numeric one is not a number. startServer(cfg) returns the Node http.Server.

import { loadConfig, startServer } from "@komaa/openai-msteams-bridge";
const server = startServer(loadConfig());
server.on("listening", () => console.log("bridge up"));

The built-in SIGTERM/SIGINT drain ends every live call gracefully (session.end + close) and then exits the process. Because a library must never exit its host, it is only wired when you ask for it (the CLI does):

startServer(loadConfig(), undefined, undefined, { handleSignals: true });

Leave it off when the bridge is embedded in a larger service and wire your own shutdown instead.

StartServerOptions.tools registers function tools the bridge executes. Each entry is a name, description, JSON schema, and handler; the handler’s returned string goes back to the model as the tool output, and a throw becomes an error output the model can recover from.

import { loadConfig, startServer, type CustomTool } from "@komaa/openai-msteams-bridge";
const tools: CustomTool[] = [
{
name: "lookup_order",
description: "Look up the status of a customer order by its order number.",
parameters: {
type: "object",
properties: { orderNumber: { type: "string", description: "e.g. KO-1234" } },
required: ["orderNumber"],
},
async handler({ orderNumber }, ctx) {
// ctx: { callId, participantCount, recordingActive, log }
ctx.log.info(`lookup_order ${String(orderNumber)}`);
return await myBackend.orderStatus(String(orderNumber)); // the agent speaks this
},
},
];
startServer(loadConfig(), undefined, undefined, { tools });

Names must not collide with the built-ins (end_call, express, show_image, look) - collisions and duplicates fail at startup. Keep handlers fast (the caller is waiting on the answer) and enforce your own timeout for slow backends.

StartServerOptions.mcpServers (or the OPENAI_MCP_SERVERS env variable) merges remote MCP tool entries into every session; the Realtime API executes them server-side - the bridge does nothing at call time.

startServer(loadConfig(), undefined, undefined, {
mcpServers: [{
server_label: "calendar",
server_url: "https://mcp.example.com/sse",
allowed_tools: ["list_events", "create_event"],
authorization: process.env.MCP_CALENDAR_TOKEN,
}],
});

See Extending the Agent’s Tools for the trust model and defaults.

The third argument to startServer is a VisionDescriber - your own answer to the agent’s look tool. The raw frame never leaves your process; only the string you return is sent to the agent.

import OpenAI from "openai";
import { loadConfig, startServer, type VisionDescriber } from "@komaa/openai-msteams-bridge";
const openai = new OpenAI(); // reads OPENAI_API_KEY
const describe: VisionDescriber = async (frame, question) => {
// frame: { source: "camera" | "screenshare", mime, dataBase64, width, height, participantName?, ... }
const who = frame.source === "screenshare" ? "the caller's shared screen" : "the caller's camera";
const res = await openai.chat.completions.create({
model: "gpt-4o-mini", // any vision-capable model
max_tokens: 300,
messages: [
{
role: "user",
content: [
{ type: "text", text: `This is ${who}. ${question}` },
{ type: "image_url", image_url: { url: `data:${frame.mime};base64,${frame.dataBase64}`, detail: "low" } },
],
},
],
});
return res.choices[0]?.message?.content ?? "I could not make out the image."; // becomes the `look` tool result
};
startServer(loadConfig(), undefined, describe);

Pass null as the third argument to disable path-2 vision entirely (the agent then falls back to the recording-gated native Realtime image attach). Omit it to use the built-in makeVisionDescriber(cfg), which is driven by VISION_API_URL.

The second argument to startServer is an OaConnector - a factory that returns an AgentPort. The default opens a real Realtime socket; tests substitute a fake so no network or API key is needed.

import { startServer, loadConfig, type OaConnector, type AgentPort } from "@komaa/openai-msteams-bridge";
const fakeConnector: OaConnector = async (_cfg, _log, handlers) => {
const port: AgentPort = {
sessionId: "sess_test",
isOpen: true,
sendAudioChunk() {},
sendSessionUpdate() {},
sendContext() {},
sendUserMessage() {},
sendToolResult() {},
requestResponse() {},
cancelResponse() {},
async attachImage() {},
close() {},
};
// push server->bridge events at any time with handlers.onMessage(...)
return port;
};
startServer(loadConfig(), fakeConnector, null);

The repository’s own test suite uses exactly this shape.

Useful if you build tools that talk to the bridge, or want to test the upgrade.

import { sign, verify, isFresh, TIMESTAMP_HEADER, SIGNATURE_HEADER } from "@komaa/openai-msteams-bridge";
const ts = Date.now();
const signature = sign(secret, ts, callId); // HMAC-SHA256(secret, `${ts}.${callId}`) hex
// send as headers X-StandIn-Timestamp / -Signature
verify(secret, ts, callId, signature); // constant-time, false on any missing input
isFresh(ts, 60_000); // within the freshness window?

The 16k/24k conversion is exported for tooling and tests: resamplePcm16(buf, fromRate, toRate) (anti-aliasing low-pass when decimating), upsampleBase64To24k, downsampleBase64To16k, and the WIRE_RATE / OPENAI_RATE constants.

All wire message types are exported for building or validating messages: SessionStartMessage, AudioFrameMessage, VideoFrameMessage, ParticipantsMessage, DtmfMessage, AssistantSayMessage, AssistantCancelMessage, ExpressionMessage, DisplayImageMessage, the WorkerInbound / WorkerOutbound unions, plus parseWorkerMessage() and pcm16kBytesToMs(). See the Wire Protocol for the full contract.

  • authorizeUpgrade, callIdFromUrl, ReplayGuard - the upgrade-authorization primitives.
  • CallSession - the per-call relay class (advanced embedding).
  • assertPublicHttpUrl, isForbiddenIp, readBodyWithCap, fetchPublicImage - the SSRF-guard primitives.
  • RealtimeAgentSocket, buildSessionUpdate, buildInstructions, synthesizeGoodbye, customToolSchema, mcpTool, BRIDGE_TOOLS - the OpenAI-side helpers.
  • renderMetrics, logger - metrics text and the minimal leveled logger.