
Company News
Socket Joins New OpenJS Program to Fund Node.js Security Work
Socket is joining the OpenJS Security Stewardship Program to fund Node.js vulnerability research, maintainer remediation, and security releases.
@pyai/twilio
Advanced tools
One-line bridge from a Twilio Media Streams phone call to a PyAI Omni voice agent — handles mu-law/PCM16 transcode, resampling, barge-in, and DTMF for you.
@pyai/twilio bridges a live Twilio call to a PyAI Omni
voice agent. Point a Twilio number at a tiny server, hand the Media Streams
WebSocket to OmniAgent.bridge(...), and your caller is instantly talking to a
real listen → think → speak agent — with sub-500 ms turn-taking, natural
barge-in, DTMF, and live transfer-to-human. You write zero audio or DSP code.
import { OmniAgent } from "@pyai/twilio";
OmniAgent.bridge(twilioWebSocket, {
apiKey: process.env.PYAI_API_KEY!,
agentId: "support-bot",
voice: "stock_sarah_style2",
persona: "You are a warm, concise support agent for Acme.",
knowledge: async (q) => myVectorSearch(q), // optional per‑turn grounding
});
That single call is a complete phone agent.
Most voice stacks are a fragile chain of four vendors: speech-to-text → an LLM → text-to-speech → a telephony bridge, each with its own latency, billing, and failure mode. Omni collapses all of it into one realtime engine behind one WebSocket and one API key:
@pyai/twilio is the last mile: it makes Omni answer a real phone number.
🎁 Get $50 in free credit when you sign up
Create a key at console.pyai.com — email only, no credit card, no sales call. That's roughly 1,000 minutes of live Omni calls to build and test with, free. Your key works on every surface instantly.
The bridge owns the entire audio path so you never touch a codec or a resampler:
omniRate: 8000 to skip
resampling entirely.configure frame (voice, persona, optional knowledge endpoint).clear, so the agent stops mid‑word.knowledge(query) callback is invoked on each
finalized caller turn; whatever you return is pushed to the agent as grounding.npm install @pyai/twilio
Requires Node ≥ 22 (uses the ws WebSocket client; everything else is built‑in).
You'll need a PyAI key (pyai_live_… or a free pyai_test_… sandbox key) —
grab one with $50 free credit — and a Twilio
number with Media Streams.
When Twilio receives a call it fetches TwiML from your webhook. Use
<Connect><Stream> (not <Start><Stream>) so the socket is two‑way and the
agent can speak back:
<?xml version="1.0" encoding="UTF-8"?>
<Response>
<Connect>
<Stream url="wss://your-host.example.com/media" />
</Connect>
</Response>
connectStreamTwiML("wss://your-host/media") builds exactly this string for you.
Set the number's A call comes in webhook to https://your-host/voice.
import Fastify from "fastify";
import websocket from "@fastify/websocket";
import { OmniAgent, connectStreamTwiML } from "@pyai/twilio";
const app = Fastify();
await app.register(websocket);
app.post("/voice", (req, reply) =>
reply.type("text/xml").send(connectStreamTwiML(`wss://${req.headers.host}/media`)));
app.get("/media", { websocket: true }, (twilioWS) =>
OmniAgent.bridge(twilioWS, { apiKey: process.env.PYAI_API_KEY, agentId: "support-bot" }));
await app.listen({ port: 8080, host: "0.0.0.0" });
Tunnel it (ngrok http 8080), point your Twilio number at https://<host>/voice,
and call the number. See examples/twilio-omni-voice-agent
for a complete, runnable version.
OmniAgent.bridge(twilioWS, options) → BridgeHandletwilioWS is the Twilio Media Streams socket (the Node ws socket your
framework hands you). Options:
| Option | Type | Notes |
|---|---|---|
apiKey | string | Required. pyai_live_… / pyai_test_…. Opaque — never parsed. |
sessionLabel | string | Optional opaque tag echoed to your kb_endpoint so you can branch per call. Nothing to pre-create; omit if unneeded. |
agentId | string | Deprecated alias for sessionLabel. |
voice | string | Voice id (stock / clone / designed). |
persona | string | System prompt / role for the agent. |
knowledge | (q) => facts | Promise<facts> | Per‑turn grounding callback. |
kbEndpoint / kbToken | string | Customer‑hosted endpoint the engine pulls per turn. |
omniRate | 8000 | 16000 | 24000 | Omni session rate. Default 24000. 8000 = no resampling. |
baseURL | string | Defaults to https://api.pyai.com. |
onTranscript / onTransfer / onError / onClose | callbacks | Observability + lifecycle. |
Returns a handle with close() and the underlying omni client.
Also exported for custom pipelines (and fully unit‑tested):
import {
muLawEncode, muLawDecode, // G.711 companding
Resampler, makeResampler, // anti-aliased rational resampler
pcm16ToBytes, bytesToPcm16, // little-endian PCM16 <-> bytes
OmniClient, omniWsUrl, // raw Omni realtime client
parseTwilioMessage, twilioMedia, // Twilio frame parse/build
twilioClear, connectStreamTwiML,
} from "@pyai/twilio";
src/omni.ts so they're trivial to update.onTransfer(info); perform the actual call redirect with Twilio's REST API
(see the example) — that needs your Twilio credentials, not PyAI's.streamSid, which is what Twilio's jitter buffer likes.npm install
npm test # node --test, mock Twilio + mock Omni sockets (no network)
npm run typecheck
npm run build # emits dist/
MIT licensed.
FAQs
One-line bridge from a Twilio Media Streams phone call to a PyAI Omni voice agent, handles mu-law/PCM16 transcode, resampling, barge-in, DTMF, and engine-native call control (transfer, hangup) for you.
The npm package @pyai/twilio receives a total of 7 weekly downloads. As such, @pyai/twilio popularity was classified as not popular.
We found that @pyai/twilio demonstrated a healthy version release cadence and project activity because the last version was released less than a year ago. It has 1 open source maintainer collaborating on the project.

Company News
Socket is joining the OpenJS Security Stewardship Program to fund Node.js vulnerability research, maintainer remediation, and security releases.

Security News
Two compromised GitHub Actions were re-enabled with malicious tags intact, exposing thousands of downstream repositories to Mini Shai-Hulud.

Research
/Security News
A malicious Firefox extension fetches its payload after installation to evade detection, steal Google session cookies, and automate account takeover.