@profullstack/group-call
v0.6.1
Published
A group call on a phone number: dial in, key a room code, talk with everyone else who did, people and AI agents alike. Telnyx Call Control state machine extracted from nixamp's party line; the engine behind nixamp live joins and group.call.pit.world.
Readme
@profullstack/group-call
A group call on a phone number. People dial in, key a room code, and talk with everyone else who keyed the same code. AI agents phone in the same way, keying the code with DTMF, so a room holds both.
It's the engine behind nixamp's live-join party line and group.call.pit.world. Audio mixing is Telnyx's. This package answers Telnyx Call Control webhooks and runs the state machine: answer, ask for the code on the keypad, join or create the conference, welcome the caller, count who is there.
npm i @profullstack/group-callimport { GroupCall } from "@profullstack/group-call";
const line = new GroupCall({
apiKey: process.env.TELNYX_API_KEY!,
publicKey: process.env.TELNYX_PUBLIC_KEY!, // the account's ed25519 key, base64
codeLength: 4,
greeting: "Welcome to group call. Enter the four digit room code.",
// Only rooms you know about open. Leave this out and any code is a room.
onCode: (code) => (rooms.has(code) ? { welcome: `Welcome to ${rooms.get(code)}. ` } : { reject: "That is not a room." }),
onJoin: (code, who) => console.log(`${who.name || who.from} joined ${code}`),
onLeave: (code, who) => console.log(`${who.name || who.from} left ${code}`),
});
// Your webhook route. Check the signature against the raw body, then hand over the event.
if (!line.verify(rawBody, headers["telnyx-signature-ed25519"], headers["telnyx-timestamp"])) return 401;
await line.handle(JSON.parse(rawBody).data);What it does
- Keypad codes only. A misheard spoken digit is a different room that also exists. It asks three times, says how many digits it got, then says goodbye.
- Conferences that outlive Telnyx's 4-hour expiry. A stale id is remade on the next join.
- A fallback voice. If the main voice fails once, that leg hears the plain voice from then on, so a broken TTS voice can't loop silently.
- ElevenLabs voices with
voiceSettings: { api_key_ref }. - Agents with names.
line.expect(code, callerId, "riotcoder")before an agent dials in, and the room knows who it is. - Call people in.
dial(code, phone, { from, connectionId, name?, prompt? })rings a phone from your number. Whoever answers presses 1 and is in the room, so voicemail never joins. The room rides in the call'sclient_state, so an answer after a restart still lands right. - Call people in from the call. With
onInvite(code, who, digits), anyone already in a room presses*, a phone number and#(*again cancels): a person on their keypad, or an agent sending DTMF on its leg. The app decides whether it may and usually callsdial; the string it returns is said to whoever asked. Without the hook, keys pressed in a room do nothing. - Call a voice agent in.
dial(code, "sip:[email protected]", { ..., direct: true, name, headers })joins on answer with no press 1 and no greeting, and sendsheaders(X-...only) on the INVITE. ElevenLabs Agents expose them as dynamic variables (X-Otr-Episode->{{sip_otr_episode}}). - Record a room.
record(code)records the conference, everyone mixed, as mp3 (or wav) until it ends orstopRecording(code).onRecording({ code, conferenceId, recordingId, mp3, wav, ... })fires onconference.recording.saved; the links are short-lived, so download straight away, or get fresh ones withrecordingLinks(recordingId). - Room API.
say(code, text, voice?)speaks into a room. Alsoparticipants(code),kick(code, leg),list(),hasCallers(code)andcallersIn(code). - App hooks.
onCodecan join, welcome, reject, or take the call over:call.gather(...)asks another question (nixamp's "press 1 for a text"). - Live transcripts. With
transcribe: true, each participant's own leg is transcribed, andonTranscript(code, who, text)gets every finished line with its speaker, agents included. Telnyx bills this per minute per leg, so it's off unless you ask for it. - Survives restarts. Conference names carry a keyed hash of the room code. After a restart, the first caller into a room joins its running conference, not a new one.
- Deploy-proof rooms. Save
participants(code)plus each room's conference id fromonJoin/onLeave, and pass them torestore()at boot. Each conference is checked against Telnyx, and the legs still in it come back with their names, so a restart neither splits a room nor forgets who is in it. telnyxSms,verifyTelnyx,newRoomCode,sameSecrethelpers.
MIT
