Streams
StreamConnector is the SDK primitive for building a Bridge.
It connects two WebSockets, Teler’s call audio stream and your AI provider’s
WebSocket, and relays messages between them through pluggable handlers.
Build a Stream Call Flow
Section titled “Build a Stream Call Flow”Your flow_url endpoint returns a Stream flow when it wants Teler to open a
WebSocket. Use CallFlow.stream() to build the JSON.
The parameter surface differs between the two SDKs: Python has no
sample_rate parameter, while Node accepts sampleRate.
| Parameter | SDK | Default | Notes |
|---|---|---|---|
ws_url / first arg | both | none | wss:// URL (TLS only). |
chunk_size / chunkSize | both | 400 | Audio chunk size. The SDK does not validate the value; the media pipeline expects a multiple of 20. |
record | both | true | Whether to record the streamed leg. |
sampleRate | Node only | "8k" | "8k" or "16k". Not available in the Python SDK. |
from teler import CallFlow
flow = CallFlow.stream( ws_url="wss://your-domain.com/media-stream", chunk_size=500, record=True,)import { CallFlow } from "@frejun/teler";
const flow = CallFlow.stream( "wss://your-domain.com/media-stream", { chunkSize: 500, sampleRate: "16k" },);StreamConnector
Section titled “StreamConnector”StreamConnector runs two coroutines/promises concurrently: one reading
the call WebSocket, one reading the remote WebSocket. Each handler decides
what to do with every message: relay it, drop it, or stop the stream.
The constructors take the same concepts but in a different argument order.
Python uses keyword arguments (remote_url after stream_type); Node takes
remoteUrl as the first positional argument, and also accepts a fifth
headers argument for the remote WebSocket.
| Purpose | Python (keyword) | Node (positional order) | Notes |
|---|---|---|---|
| Stream direction | stream_type | 2nd: streamType | Defaults to BIDIRECTIONAL. UNIDIRECTIONAL raises NotImplementedException. |
| Remote WebSocket URL | remote_url | 1st: remoteUrl | Required. Empty/unparseable raises BadParametersException. |
| Teler → remote handler | call_stream_handler | 3rd: callStreamHandler | Python defaults to relay-all; Node requires it. |
| Remote → Teler handler | remote_stream_handler | 4th: remoteStreamHandler | Python defaults to relay-all; Node requires it. |
| Remote WS headers | — | 5th: headers | Node only. Defaults to {}. |
from teler import StreamConnector, StreamOpfrom teler.streams import StreamType
connector = StreamConnector( stream_type=StreamType.BIDIRECTIONAL, remote_url="wss://your-ai-provider.example.com", call_stream_handler=..., remote_stream_handler=...,)import { StreamConnector, StreamOP, StreamType } from "@frejun/teler";
const connector = new StreamConnector( "wss://your-ai-provider.example.com", StreamType.BIDIRECTIONAL, callStreamHandler, remoteStreamHandler,);StreamOp
Section titled “StreamOp”Every handler returns (data, StreamOp). The op tells the connector what
to do with data. The enum is named StreamOp in Python and StreamOP in
Node.
| Op | Effect |
|---|---|
RELAY | Forward to the other WebSocket. |
PASS | Not forwarded. (Effectively drops the message; only RELAY and STOP are acted on.) |
STOP | Close the WebSocket(s) cleanly and stop the bridge. |
# Relay everything (default)async def relay(message: str): return (message, StreamOp.RELAY)// Relay everything (default)const relay = async (payload) => [payload, StreamOP.RELAY];Handler signature
Section titled “Handler signature”Handlers are async. Python takes a raw JSON string; Node takes a structured
StreamData object. Both must return a 2-tuple of (data, StreamOp).
Returning the wrong shape raises BadParametersException.
from typing import Tuplefrom teler import StreamOp
async def my_handler(message: str) -> Tuple[str, StreamOp]: # `message` is the raw JSON from the WebSocket. return (message, StreamOp.RELAY)import { StreamOP } from "@frejun/teler";
const myHandler = async (payload) => { // `payload` is a StreamData value (string | Buffer | Uint8Array | ...). return [payload, StreamOP.RELAY];};Run the bridge
Section titled “Run the bridge”The connector races both directions and exits when either side completes:
caller hangs up, provider closes, or a handler returns STOP.
# Inside your /media-stream WebSocket handlerawait connector.bridge_stream(call_ws)await connector.bridgeStream(callWs);Minimal echo Bridge
Section titled “Minimal echo Bridge”Drop-in starting point for any recipe. Single column here, full file shown.
import asynciofrom fastapi import FastAPI, WebSocketfrom teler import StreamConnector, StreamOp, CallFlow
app = FastAPI()
@app.post("/flow")async def flow(): return CallFlow.stream( ws_url="wss://your-domain.com/media-stream", chunk_size=400, record=True, )
async def relay(message: str): return (message, StreamOp.RELAY)
@app.websocket("/media-stream")async def media_stream(call_ws: WebSocket): await call_ws.accept() connector = StreamConnector( remote_url="wss://your-ai-provider.example.com", call_stream_handler=relay, remote_stream_handler=relay, ) await connector.bridge_stream(call_ws)import express from "express";import { WebSocketServer } from "ws";import { Client, CallFlow, StreamConnector, StreamOP, StreamType } from "@frejun/teler";
const app = express();
app.post("/flow", (_req, res) => { res.json(CallFlow.stream("wss://your-domain.com/media-stream", { chunkSize: 400, sampleRate: "16k", }));});
const server = app.listen(3000);const wss = new WebSocketServer({ server, path: "/media-stream" });
const relay = async (payload) => [payload, StreamOP.RELAY];
wss.on("connection", async (callWs) => { const connector = new StreamConnector( "wss://your-ai-provider.example.com", StreamType.BIDIRECTIONAL, relay, relay, ); await connector.bridgeStream(callWs);});