brief-vase-33757
05/12/2025, 4:17 PMtall-airline-16756
05/12/2025, 4:17 PMtall-airline-16756
05/12/2025, 4:17 PMbrief-vase-33757
05/12/2025, 4:18 PMtall-airline-16756
05/12/2025, 4:18 PMroom.disconnect()
is called manually by the user.
• Due to an unrecoverable connection issue.
• Server-initiated actions, such as:
◦ DUPLICATE_IDENTITY: Another client with the same identity joined the room.
◦ PARTICIPANT_REMOVED: The participant was removed via the RemoveParticipant API.
◦ ROOM_DELETED: The room was closed via the DeleteRoom API.
◦ JOIN_FAILURE: Failure to connect to the room, possibly due to network issues.
◦ ROOM_CLOSED: The room was closed because all Standard and Ingress participants left.
If you need further details about any of these cases, feel free to ask!
Sources: Connecting to LiveKit | LiveKit Docs | RoomEvent | LiveKit JS Client SDK - v2.8.1brief-vase-33757
05/12/2025, 4:18 PMtall-airline-16756
05/12/2025, 4:18 PMbrief-vase-33757
05/12/2025, 4:28 PMtall-airline-16756
05/12/2025, 4:29 PMCreateSIPParticipant
.
3. The user answers the call and starts speaking to the agent.
Code Example:
# add these imports at the top of your file
from livekit import api
import json
# ... any existing code / imports ...
def entrypoint(ctx: agents.JobContext):
await ctx.connect()
# If a phone number was provided, then place an outbound call
dial_info = json.loads(ctx.job.metadata)
phone_number = dial_info["phone_number"]
# The participant's identity can be anything you want, but this example uses the phone number itself
sip_participant_identity = phone_number
if phone_number is not None:
# The outbound call will be placed after this method is executed
try:
await ctx.api.sip.create_sip_participant(api.CreateSIPParticipantRequest(
# This ensures the participant joins the correct room
room_name=ctx.room.name,
# This is the outbound trunk ID to use (i.e. which phone number the call will come from)
# You can get this from LiveKit CLI with `lk sip outbound list`
sip_trunk_id='ST_xxxx',
# The outbound phone number to dial and identity to use
sip_call_to=phone_number,
participant_identity=sip_participant_identity,
# This will wait until the call is answered before returning
wait_until_answered=True,
))
print("call picked up successfully")
except api.TwirpError as e:
print(f"error creating SIP participant: {e.message}, "
f"SIP status: {e.metadata.get('sip_status_code')} "
f"{e.metadata.get('sip_status')}")
ctx.shutdown()
# .. create and start your AgentSession as normal ...
# Add this guard to ensure the agent only speaks first in an inbound scenario.
# When placing an outbound call, its more customary for the recipient to speak first
# The agent will automatically respond after the user's turn has ended.
if phone_number is None:
await session.generate_reply(
instructions="Greet the user and offer your assistance."
)
You can trigger this outbound call using either the LiveKit CLI or Python API, providing the phone number to call in the job's metadata. Let me know if you need further clarification!
Sources: Agents telephony integration | LiveKit Docsbrief-vase-33757
05/12/2025, 4:31 PMtall-airline-16756
05/12/2025, 4:31 PMctx.api.sip.create_sip_participant
, you can use one of the following methods:
1. *Use the wait_until_answered
parameter:*Set wait_until_answered=True
in the CreateSIPParticipantRequest
. This will make the method wait until the call is answered before returning:
await ctx.api.sip.create_sip_participant(api.CreateSIPParticipantRequest(
room_name=ctx.room.name,
sip_trunk_id='ST_xxxx',
sip_call_to=phone_number,
participant_identity=sip_participant_identity,
# This will wait until the call is answered before returning
wait_until_answered=True,
))
print("call picked up successfully")
2. *Monitor the sip.callStatus
attribute:*If you don’t use wait_until_answered
, the method returns immediately after dialing is initiated. In this case, you can check the sip.callStatus
attribute to monitor the dialing status:
`start_time = perf_counter()
while perf_counter() - start_time < 30:
call_status = participant.attributes.get("sip.callStatus")
if call_status == "active":
logger.info("user has picked up")
return
elif call_status == "automation":
# if DTMF is used in the sip_call_to
number, typically used to dial
# an extension or enter a PIN.
# during DTMF dialing, the participant will be in the "automation" state
pass
elif call_status == "hangup":
# user hung up, we’ll exit the job
logger.info("user hung up, exiting job")
break
await asyncio.sleep(0.1)`When the user answers, the sip.callStatus
attribute updates to active
.
Let me know if you need further clarification or assistance!
Sources: Making calls using SIP | LiveKit Docs | Agents telephony integration | LiveKit Docs