Skip to content

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.

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.

ParameterSDKDefaultNotes
ws_url / first argbothnonewss:// URL (TLS only).
chunk_size / chunkSizeboth400Audio chunk size. The SDK does not validate the value; the media pipeline expects a multiple of 20.
recordbothtrueWhether to record the streamed leg.
sampleRateNode 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,
)

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.

PurposePython (keyword)Node (positional order)Notes
Stream directionstream_type2nd: streamTypeDefaults to BIDIRECTIONAL. UNIDIRECTIONAL raises NotImplementedException.
Remote WebSocket URLremote_url1st: remoteUrlRequired. Empty/unparseable raises BadParametersException.
Teler → remote handlercall_stream_handler3rd: callStreamHandlerPython defaults to relay-all; Node requires it.
Remote → Teler handlerremote_stream_handler4th: remoteStreamHandlerPython defaults to relay-all; Node requires it.
Remote WS headers5th: headersNode only. Defaults to {}.
from teler import StreamConnector, StreamOp
from teler.streams import StreamType
connector = StreamConnector(
stream_type=StreamType.BIDIRECTIONAL,
remote_url="wss://your-ai-provider.example.com",
call_stream_handler=...,
remote_stream_handler=...,
)

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.

OpEffect
RELAYForward to the other WebSocket.
PASSNot forwarded. (Effectively drops the message; only RELAY and STOP are acted on.)
STOPClose the WebSocket(s) cleanly and stop the bridge.
# Relay everything (default)
async def relay(message: str):
return (message, StreamOp.RELAY)

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 Tuple
from teler import StreamOp
async def my_handler(message: str) -> Tuple[str, StreamOp]:
# `message` is the raw JSON from the WebSocket.
return (message, StreamOp.RELAY)

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 handler
await connector.bridge_stream(call_ws)

Drop-in starting point for any recipe. Single column here, full file shown.

import asyncio
from fastapi import FastAPI, WebSocket
from 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)