adorable-action-64889
04/10/2026, 5:57 AMliveagent-js with outbound call using SIP trunktall-airline-16756
04/10/2026, 5:57 AMtall-airline-16756
04/10/2026, 5:58 AMliveagent-js (with token creation flow notes if needed).
, with interactive elementsadorable-action-64889
04/10/2026, 5:58 AMtall-airline-16756
04/10/2026, 5:58 AMlivekit-agent-js and notes you’ll need. This responds to your request for a Node.js example using a SIP trunk.
Prerequisites
• A LiveKit project with a configured SIP trunk and its sip_trunk_id.
• An agent dispatched to a room where the agent can access ctx.room.localParticipant.
• Dependencies installed:
pnpm add @livekit/agents livekit-server-sdk
Minimal Node.js agent example (TypeScript)
import { defineAgent, JobContext, log } from '@livekit/agents';
export default defineAgent({
entry: async (ctx: JobContext) => {
const logger = log();
await ctx.connect();
logger.info('Connected to room');
const sipTrunkId = 'your-sip-trunk-id'; // replace
const targetNumber = '+1234567890'; // replace E.164 number or SIP URI
const participantIdentity = 'outbound-sip';
const createRequest = {
sip_trunk_id: sipTrunkId,
sip_call_to: targetNumber,
room_name: ctx.room.name,
participant_identity: participantIdentity,
play_dialtone: true,
krisp_enabled: true,
wait_until_answered: true,
};
try {
await ctx.room.localParticipant.createSIPParticipant(createRequest);
logger.info(`Outbound call initiated to ${targetNumber}`);
} catch (err: any) {
logger.error('Failed to create SIP participant', { error: err.message });
}
},
});
Notes and options
• sip_call_to accepts a phone number (E.164) or SIP URI depending on your trunk.
• wait_until_answered: true makes the call creation wait until the remote party answers.
• krisp_enabled enables LiveKit’s noise cancellation for better audio.
• To include DTMF, custom SIP headers, or call recording, you’ll need to add those fields to the create request per your SIP trunk and LiveKit configuration.
If you want, tell me which of the following you need next and I’ll provide the exact snippet:
• Creating the SIP trunk programmatically (server SDK).
• Adding custom SIP headers or credentials.
• DTMF or call recording configuration.
• A JavaScript (non-TypeScript) version. Sources: examples/voice_agents | livekit/livekit_composite | DeepWiki | Agents telephony integration | LiveKit Documentation | Preserving Caller ID During SIP Transfers in LiveKit Agents | The Human-in-the-Loop (HITL) Pattern for Voice Agents | LiveKit, with interactive elementsadorable-action-64889
04/10/2026, 6:01 AMtall-airline-16756
04/10/2026, 6:01 AMvoice.AgentSession just to initiate the outbound SIP call with the sample code. Creating a SIP participant via ctx.room.localParticipant.createSIPParticipant() is sufficient to place the call and join the room.
• Use voice.AgentSession only when you need the voice AI/media orchestration features — for example, managed STT/TTS, LLM-driven conversational loops, or real‑time voice processing within the room. AgentSession handles the media/AI pipelines; simple signaling/connectivity does not require it.
If you plan to do any TTS, STT, or conversational AI with the caller, tell me which features you need and I’ll show how to start and use voice.AgentSession in your agent code.
Sources: Agents Framework | livekit/livekit_composite | DeepWiki | sip | livekit/livekit_composite | DeepWiki | Agent session | LiveKit Documentation | OpenAI and LiveKit partner to turn Advanced Voice into an API | LiveKit, with interactive elementsadorable-action-64889
04/10/2026, 6:02 AMtall-airline-16756
04/10/2026, 6:03 AMvoice.AgentSession for media/AI orchestration and createSIPParticipant to place the outbound call.
Prerequisites
• SIP trunk configured in your LiveKit project and you have sip_trunk_id.
• Dependencies:
pnpm add @livekit/agents livekit-server-sdk
High-level flow
1. Define the agent with LLM/STT/TTS configs.
2. In the agent entry, connect and create a voice.AgentSession to manage STT/TTS/LLM pipelines.
3. After session is ready, call ctx.room.localParticipant.createSIPParticipant() to initiate the outbound call into the same room.
4. The AgentSession will handle audio input/output for conversation with the phone participant.
Minimal example
import { defineAgent, JobContext, log } from '@livekit/agents';
import * as voice from '@livekit/agents/voice'; // adjust import path per package
export default defineAgent({
entry: async (ctx: JobContext) => {
const logger = log();
await ctx.connect();
logger.info('Agent connected to room');
// 1) Start AgentSession to manage STT/TTS/LLM
const agentSession = new voice.AgentSession({
room: ctx.room,
localParticipant: ctx.room.localParticipant,
llm: {
provider: 'openai', // example provider config
model: 'gpt-4o-mini', // example
// apiKey, etc. configured via environment or agent config
},
stt: { provider: 'whisper', language: 'en-US' },
tts: { provider: 'polly', voice: 'Matthew' },
// any other session options (silence detection, partial transcripts, etc.)
});
await agentSession.start(); // prepares pipelines, handlers, etc.
logger.info('AgentSession started (STT/TTS/LLM ready)');
// 2) Initiate outbound SIP call that will join the same room
const sipTrunkId = 'your-sip-trunk-id';
const targetNumber = '+1234567890';
try {
const createRequest = {
sip_trunk_id: sipTrunkId,
sip_call_to: targetNumber,
room_name: ctx.room.name,
participant_identity: 'phone-participant',
play_dialtone: true,
krisp_enabled: true,
wait_until_answered: true,
};
await ctx.room.localParticipant.createSIPParticipant(createRequest);
logger.info(`Outbound SIP call initiated to ${targetNumber}`);
} catch (err: any) {
logger.error('Failed to create SIP participant', { error: err.message });
throw err;
}
// 3) Wire up conversation handlers (example)
agentSession.on('transcript', (text) => {
logger.info('Transcript:', text);
// optional: send transcript to LLM or take action
});
agentSession.on('ended', () => {
logger.info('AgentSession ended');
});
// Keep the agent running to handle conversation; cleanup when appropriate
// await agentSession.stop() when finished
},
});
Important notes
• The AgentSession is required for STT/TTS/LLM orchestration. Creating the SIP participant without AgentSession will place the call but the agent won’t have the managed media/AI pipeline.
• Start AgentSession before or immediately around the time you create the SIP participant so audio streams are correctly attached when the phone participant joins.
• Use wait_until_answered: true if you want the create call to block until the callee answers, ensuring the session is ready when the call connects.
• Add handlers for transcripts, partial results, and TTS output routing as needed for your LLM conversation flow.
• If you need code for specific LLM/STT/TTS providers (detailed config, API keys) or DTMF/recording/custom SIP headers, tell me which provider(s) and I’ll add the exact config snippets.
Would you like a plain JavaScript version or a sample showing LLM prompt flow reacting to transcripts?
, with interactive elementsadorable-action-64889
04/10/2026, 6:08 AMfailed to recognize speech
before SIP trunk participant joins. What's the problem?tall-airline-16756
04/10/2026, 6:08 AMwait_until_answered: true on createSIPParticipant so the call blocks until answered.
◦ Or wait for the participant join event (e.g., ctx.wait_for_participant() or listen for room.on('participantConnected', ...)) before calling agentSession.start() or enabling recognition.
• Ensure the SIP trunk successfully establishes RTP audio when answered (trunk config/auth must be correct).
• If you must start earlier, handle transient empty-audio errors by retrying STT when audio data appears.
Example pattern (concise)
1. Create AgentSession but don’t start STT/TTS processing yet.
2. Initiate outbound call with wait_until_answered: true (or wait for participant join).
3. Once the SIP participant is connected, call agentSession.start() (or enable STT) so audio is present.
If you’d like, paste the relevant snippets (agent start, createSIPParticipant call, and logs) and I’ll point out exactly where to wait or change timing.
Sources: examples/voice_agents | livekit/livekit_composite | DeepWiki | Sequential Pipeline Architecture for Voice Agents | LiveKit | Agent session | LiveKit Documentation, with interactive elementsadorable-action-64889
04/10/2026, 6:11 AMctx.connect() then? why can it be called before SIP trunk connection and agent start?tall-airline-16756
04/10/2026, 6:12 AMctx.connect() does: it connects the agent to the LiveKit room and signaling layer and initializes the agent's local participant (so you have a room context, can publish/subscribed tracks, and call room APIs).
• Why it can be called earlier: ctx.connect() only establishes the connection and signaling state — it does not imply any remote audio is present. Starting the SIP trunk call and actual RTP audio flow are separate steps. Calling ctx.connect() early lets your agent prepare room state, handlers, and local tracks before the phone participant joins.
• Why you still must wait to start STT/TTS: speech processing depends on actual audio streams. Even though you’re connected, no remote audio exists until the SIP participant answers and RTP begins. Start or enable your voice.AgentSession (or STT) after the SIP participant has joined or use wait_until_answered: true / ctx.wait_for_participant() to avoid “failed to recognize speech” errors.
• Extra tips:
◦ Use ctx.connect({ auto_subscribe: ... }) or limit subscriptions until the participant is present to save bandwidth.
◦ You can create and publish local TTS tracks early, but only enable STT/processing once audio arrives.
If you want, I can show the exact sequence in code that calls ctx.connect(), then creates the SIP participant with wait_until_answered, and only starts AgentSession after the participant is confirmed. Which format do you prefer (TypeScript or plain JS)?
, with interactive elementsadorable-action-64889
04/10/2026, 6:43 AMvoice.AgentSession.start() , then create SIP participant (with wait_for_participant true, and finally ctx.connect()tall-airline-16756
04/10/2026, 6:44 AMvoice.AgentSession.start() needs a connected LiveKit room/local participant and access to tracks. If you call it before ctx.connect(), the session won’t have the room context or media plumbing it needs.
• Creating a SIP participant also requires the room to be connected so LiveKit can place the participant into that room. Calling createSIPParticipant before ctx.connect() is invalid.
• If you somehow start STT before remote audio is present (even if you later create the SIP participant), you’ll get “failed to recognize speech” because no RTP/audio stream exists yet.
Recommended sequence
1. Call ctx.connect() first — establishes the room, local participant, and signaling.
2. Option A (preferred for guaranteed audio): Create the SIP participant with wait_until_answered: true so the call blocks until answered.
3. After the SIP participant is joined (or after `ctx.wait_for_participant()`/participantConnected event), call voice.AgentSession.start() to enable STT/TTS/LLM processing.
◦ This ensures audio streams are present when STT starts.
Alternate (non-blocking) pattern
1. ctx.connect()
2. Create SIP participant without waiting.
3. Listen for participant join event (participantConnected) and only then call agentSession.start().
Why this matters
• ctx.connect() provides the necessary room context.
• Starting the AgentSession only after the phone participant joins prevents empty-audio STT errors.
• Using wait_until_answered simplifies timing by ensuring participant presence before you start processing.
If you want, I’ll show a concise TypeScript snippet for the preferred flow (connect → create SIP with wait → start AgentSession), or the event-driven alternative. Which one would you like?
, with interactive elementsadorable-action-64889
04/10/2026, 6:45 AMadorable-action-64889
04/10/2026, 6:46 AMtall-airline-16756
04/10/2026, 6:46 AMwait_until_answered: true, then start the voice.AgentSession after the participant is in the room. Comments show why each step is ordered this way.
import { defineAgent, JobContext, log } from '@livekit/agents';
import * as voice from '@livekit/agents/voice';
export default defineAgent({
entry: async (ctx: JobContext) => {
const logger = log();
// 1) Connect to room & signaling so room/localParticipant exist
await ctx.connect();
logger.info('Connected to room');
// 2) Place outbound SIP call and block until answered so audio will be ready
const sipTrunkId = 'your-sip-trunk-id';
const targetNumber = '+1234567890';
try {
await ctx.room.localParticipant.createSIPParticipant({
sip_trunk_id: sipTrunkId,
sip_call_to: targetNumber,
room_name: ctx.room.name,
participant_identity: 'phone-participant',
play_dialtone: true,
krisp_enabled: true,
wait_until_answered: true, // ensures participant joins before continuing
});
logger.info('SIP participant answered and joined room');
} catch (err: any) {
logger.error('Failed to create SIP participant', { error: err.message });
throw err;
}
// 3) Now start AgentSession so STT/TTS/LLM pipelines run with active audio present
const agentSession = new voice.AgentSession({
room: ctx.room,
localParticipant: ctx.room.localParticipant,
llm: { provider: 'openai', model: 'gpt-4o-mini' }, // example
stt: { provider: 'whisper', language: 'en-US' },
tts: { provider: 'polly', voice: 'Matthew' },
});
await agentSession.start();
logger.info('AgentSession started (STT/TTS/LLM ready)');
// Hook up transcript/events as needed
agentSession.on('transcript', (t) => logger.info('Transcript:', t));
agentSession.on('ended', () => logger.info('AgentSession ended'));
// Keep running until you choose to stop
// await agentSession.stop() when finished
},
});
This sequence ensures the room and SIP participant (and thus RTP audio) exist before starting STT, preventing "failed to recognize speech" errors. If you want an event-driven alternative (create SIP without waiting and start the session on participantConnected), I can provide that snippet.
, with interactive elements