how do I check if a participant is over telephony ...
# ask-ai
w
how do I check if a participant is over telephony or using console
t
Thanks for reaching out, Marius. I'm an AI assistant and I'm here to help. Give me a moment while I research this topic.
Thanks — you want to detect whether a participant is connected via telephony (SIP) or via the console (standard WebRTC). Here’s how to do that and a couple of extra checks you can use. Short answer • Check the participant’s
kind
field. ◦
PARTICIPANT_KIND_SIP
→ participant is connected via telephony (SIP). ◦
PARTICIPANT_KIND_STANDARD
→ participant is a regular WebRTC participant (console/local testing will appear as standard). Example (Python)
Copy code
if participant.kind == rtc.ParticipantKind.PARTICIPANT_KIND_SIP:
    print("Participant is connected via telephony (SIP)")
else:
    print("Participant is not using telephony (e.g., console or standard WebRTC client)")
Other useful indicators • SIP participants often include SIP-specific attributes when available, such as
sip.callStatus
,
sip.phoneNumber
, or
sip.trunkID
. Checking for those fields can provide additional confirmation. • Console/local test participants are still standard WebRTC participants unless explicitly bridged to SIP, so they will normally show as
PARTICIPANT_KIND_STANDARD
. If you’d like, tell me: • which SDK/language you’re using (JS, Python, Go, etc.) and I’ll give a code snippet for that environment, or • an example participant object you’re seeing and I’ll point out the fields to inspect. Sources: SIP Lifecycle Management Agent | LiveKit Documentation | Agents telephony integration | LiveKit Documentation | Voice AI quickstart | LiveKit Documentation
Thanks — I see two things you’re trying to do: detect whether a participant is SIP vs standard, and you’ve hit a Pylance error saying
"rtc" is not defined
. I can help with both. First, quick recap of how to detect telephony vs console: • Check the participant’s
kind
field: ◦
PARTICIPANT_KIND_SIP
→ telephony (SIP). ◦
PARTICIPANT_KIND_STANDARD
→ regular WebRTC (console/local tests appear as standard). • You can also look for SIP-specific fields such as
sip.callStatus
,
sip.phoneNumber
, or
sip.trunkID
for extra confirmation. Now about your Pylance error `"rtc" is not defined`: • That error means your code references
rtc
but you haven’t imported or defined it in your Python file. • To fix this I need to know which LiveKit Python package or client you are using (official LiveKit Python SDK, a generated protobuf module, or a local module named
rtc
). Can you tell me: 1. Which SDK/library and version you’re using for Python (package name from pip or repository)? 2. The minimal code around the line producing the error (lines ~70–90) so I can see how
participant
and
rtc
are referenced? 3. Whether you expected
rtc
to be an import (e.g.,
import rtc
or
from livekit import rtc
) or a variable created earlier? If you’d like, here’s a likely fix patterns you can try while you share the details: • If the SDK exposes
rtc
under `livekit`:
Copy code
from livekit import rtc
  # then use rtc.ParticipantKind...
• If the SDK exposes ParticipantKind directly:
Copy code
from livekit.rtc import ParticipantKind
  if participant.kind == ParticipantKind.PARTICIPANT_KIND_SIP:
      ...
• If you’re using generated protobufs, import the generated module that defines
ParticipantKind
(replace
generated_module
with the actual module name):
Copy code
from generated_module import rtc as rtc_pb
  if participant.kind == rtc_pb.ParticipantKind.PARTICIPANT_KIND_SIP:
      ...
Share the SDK/package name and the snippet around the error and I’ll give the exact import and corrected snippet to resolve the Pylance warning.
Thanks — I can help with both the SIP/console check and the Pylance error you pasted. You’re seeing a lint/type error saying
"participant" is not defined
, which means the variable you’re checking against (
participant.kind
) isn’t in scope where you’re using it. A few quick questions so I can give an exact fix: • Which SDK/language are you using (JavaScript/TypeScript, Python, Go, etc.)? • Where in your code are you trying to check the participant’s kind? (for example: inside an event handler like
participantJoined
/
on("participantConnected")
, inside a loop over
room.participants
, or elsewhere) • If possible, paste the surrounding 10–20 lines of code where the error occurs (redact any secrets). General guidance while you gather that: 1. Ensure you have a valid participant object in scope before checking
participant.kind
. ◦ Common patterns: ▪︎ In JS/TS event handlers:
room.on('participantConnected', participant => { /* use participant here */ })
▪︎ When iterating participants:
for (const participant of room.participants.values()) { /* use participant */ }
▪︎ When accessing the local participant: use the SDK’s
room.localParticipant
(name may vary by SDK) 1. Add a defensive check to avoid runtime errors while you debug:
Copy code
if participant is None:
    # handle missing participant (log, return, fetch participant)
elif participant.kind == rtc.ParticipantKind.PARTICIPANT_KIND_SIP:
    # SIP handling
else:
    # standard/WebRTC handling
(Adjust syntax for your language — I’ll provide a language-specific snippet once you tell me which SDK you’re using.) 1. If your linter still flags the variable: ◦ Confirm the variable name is spelled consistently. ◦ Ensure the variable is declared/received as a function parameter or defined in the same scope. ◦ In TypeScript, ensure types are imported and
participant
is typed (e.g.,
participant: Participant
) or use a narrow
any
while debugging. If you share the SDK/language and the code block where the error appears, I’ll give a tailored code example that shows how to obtain the
participant
object and check
participant.kind
(including handling SIP-specific fields like
sip.phoneNumber
if present).
w
in this line: if participant.kind == rtc.ParticipantKind.PARTICIPANT_KIND_SIP: I get participant is not defined
t
Thanks — that helps. You’re getting the "participant is not defined" error because there’s no
participant
variable in scope at the line where you’re using it. I can give an exact fix, but first I need a couple quick details: 1. Which SDK / language are you using? (JavaScript/TypeScript, Python, Go, etc.) 2. Where in your code are you running this check? For example: ◦ Inside an event handler like
participantConnected
/
on("participantConnected")
◦ While iterating
room.participants
◦ In a separate function where you expect to receive a participant object While you paste that code, here are the typical ways to get a participant object so the check works. Pick the pattern that matches your setup: • Event handler (JS/TS example)
Copy code
room.on('participantConnected', (participant) => {
  if (participant.kind === rtc.ParticipantKind.PARTICIPANT_KIND_SIP) {
    // SIP participant
  } else {
    // standard/WebRTC
  }
});
• Iterating current participants (JS/TS)
Copy code
for (const participant of room.participants.values()) {
  if (participant.kind === rtc.ParticipantKind.PARTICIPANT_KIND_SIP) {
    // handle SIP
  }
}
• Accessing the local participant (name may vary by SDK)
Copy code
const participant = room.localParticipant;
if (participant && participant.kind === rtc.ParticipantKind.PARTICIPANT_KIND_SIP) {
  // SIP
}
• Defensive check (Python-style pseudocode)
Copy code
if participant is None:
    # handle missing participant
elif participant.kind == rtc.ParticipantKind.PARTICIPANT_KIND_SIP:
    # SIP
else:
    # standard
If you share the SDK and the 10–20 lines of code around where the error appears (redact secrets), I’ll point out exactly where you need to obtain or pass the
participant
variable and give a ready-to-drop code snippet.
w
give me the example in pytho
t
Thanks — I’ll provide a clear Python example that shows how to obtain a participant object (so you don’t get the "participant is not defined" error) and check whether it’s a SIP participant or a standard/WebRTC participant. Below are three common patterns you can use depending on where you run the check: inside an event handler, iterating existing participants in a room, or checking the local participant. 1) Inside an event handler (recommended when reacting to new joins)
Copy code
# Example assumes you have an SDK Room object named `room`
# and an rtc module with ParticipantKind enum available as rtc.ParticipantKind

def on_participant_connected(participant):
    # participant is provided by the event callback -> in scope here
    if participant.kind == rtc.ParticipantKind.PARTICIPANT_KIND_SIP:
        print("Participant connected via telephony (SIP)")
    else:
        print("Participant is a standard/WebRTC participant (console/local)")

# Register the handler with your room object (API name may vary by SDK)
room.on("participantConnected", on_participant_connected)
2) Iterating current participants in the room
Copy code
# room.participants is commonly a dict or map of participantId -> Participant
for participant_id, participant in room.participants.items():
    if participant.kind == rtc.ParticipantKind.PARTICIPANT_KIND_SIP:
        print(f"{participant_id} is connected via SIP")
    else:
        print(f"{participant_id} is a standard/WebRTC participant")
3) Checking the local participant
Copy code
# If you need to check the local participant
participant = room.local_participant  # name may vary: localParticipant/local_participant
if participant is None:
    print("No local participant available yet")
elif participant.kind == rtc.ParticipantKind.PARTICIPANT_KIND_SIP:
    print("Local participant is SIP")
else:
    print("Local participant is standard/WebRTC")
Defensive pattern to avoid the "participant is not defined" error • Ensure the variable
participant
is either: ◦ passed into your function (event callback), or ◦ obtained from
room.participants
or
room.local_participant
before checking
participant.kind
. • Add a None-check before accessing `.kind`:
Copy code
if participant is None:
    # handle missing participant
elif participant.kind == rtc.ParticipantKind.PARTICIPANT_KIND_SIP:
    # handle SIP
else:
    # handle standard
Optional extra: check SIP-specific fields • SIP participants often include SIP fields like
sip.callStatus
,
sip.phoneNumber
, or
sip.trunkID
when available. You can check those attributes for additional confirmation if your participant objects expose them. If you share which LiveKit Python SDK module names you have imported (or paste the 10–20 lines around where you tried the check), I’ll tailor the snippet to your exact API names and show how to register the handler or access
room
correctly.
w
I am trying to check it in an onenter
t
Thanks — since you’re checking inside an on-enter handler, the key is to use the participant object passed into that callback so
participant
is in scope. Below are two Python patterns you can use depending on how your SDK/event system names the event: one where the handler receives the participant directly, and one where you receive an event object that contains the participant. 1) Handler that receives the participant directly
Copy code
# Example: room.on("participantConnected", handler)
def on_enter(participant):
    # participant is provided by the event -> safe to use here
    if participant.kind == rtc.ParticipantKind.PARTICIPANT_KIND_SIP:
        print("Participant entered via telephony (SIP)")
    else:
        print("Participant entered as standard/WebRTC (console/local)")

# Register the handler (event name may vary by SDK)
room.on("participantConnected", on_enter)
2) Handler that receives an event object containing the participant
Copy code
def on_enter_event(event):
    # event may contain participant as event.participant or event.data['participant']
    participant = getattr(event, "participant", None) or event.data.get("participant")
    if participant is None:
        print("No participant in event")
        return

    if participant.kind == rtc.ParticipantKind.PARTICIPANT_KIND_SIP:
        print("Participant entered via telephony (SIP)")
    else:
        print("Participant entered as standard/WebRTC (console/local)")
Defensive checks (recommended) • Always confirm
participant
is not None before accessing
.kind
. • If your linter/type checker still flags the variable, ensure the callback signature matches the SDK’s event signature (parameter name and order). Extra confirmation (optional) • For added certainty, check SIP-specific fields when present (for example
participant.sip.callStatus
,
participant.sip.phoneNumber
, or
participant.sip.trunkID
) — these fields often exist for SIP participants. If you share the exact event name your SDK uses for "on enter" and a short snippet of how you registered the handler, I’ll tailor the example to match your API (and fix any scoping issues that cause the "participant is not defined" error).
w
would it look like this then? async def on_enter(self, participant) -> None:
t
Yes — that signature is correct for an async handler that receives the participant. Here’s a complete, ready-to-drop example you can use. It shows the async handler, defensive checks to avoid the "participant is not defined" error, and the SIP vs standard check.
Copy code
# Example async on_enter handler
# Adjust register syntax to match your SDK if different

async def on_enter(self, participant) -> None:
    # Ensure participant was passed into the handler
    if participant is None:
        print("No participant provided to on_enter")
        return

    # Check participant kind for SIP vs standard/WebRTC
    if participant.kind == rtc.ParticipantKind.PARTICIPANT_KIND_SIP:
        print("Participant entered via telephony (SIP)")
        # Optional: check SIP-specific fields if available
        # e.g., print(participant.sip.phoneNumber)
    else:
        print("Participant entered as standard/WebRTC (console/local)")
How to register it (example patterns — use the one that matches your SDK) • If the SDK expects a bound method:
Copy code
# assuming this is a method on a class instance 'handler_instance'
room.on("participantConnected", handler_instance.on_enter)
• If the SDK expects a simple callback function:
Copy code
room.on("participantConnected", on_enter)
Notes and tips • The key reason you saw "participant is not defined" is using
participant
outside the function where it’s not in scope. With the handler signature above,
participant
is a parameter and is available inside the function. • Keep the None-check to avoid attribute errors while participants are still initializing. • If your linter still flags things, confirm the event actually calls the handler with the participant as the first argument (some SDKs pass an event object instead). If the SDK passes an event object, use:
Copy code
async def on_enter(self, event) -> None:
    participant = getattr(event, "participant", None) or event.data.get("participant")
    ...
If you share how you register the handler with your
room
(the exact code line), I’ll confirm the correct registration form for your SDK and adapt the example if needed.
Thanks — that error explains exactly what's happening. The LiveKit agent framework is calling your agent’s on_enter without passing a participant argument, so defining
on_enter(self, participant)
causes the TypeError. What to change • Define
on_enter
with only
self
(no participant parameter). • Inside
on_enter
, get the participant from
self.session.room_io.linked_participant
. • Then check
participant.kind
to detect SIP vs standard. Ready-to-use example
Copy code
from livekit import rtc
from livekit.agents import Agent

class RegNumAgent(Agent):
    def __init__(self) -> None:
        super().__init__(instructions="You are a helpful agent.")

    async def on_enter(self) -> None:
        # Obtain the participant the agent is handling
        participant = self.session.room_io.linked_participant

        if participant is None:
            # defensive: participant may not be set yet
            await self.session.generate_reply(instructions="No participant linked yet.")
            return

        # Check SIP vs standard/WebRTC
        if participant.kind == rtc.ParticipantKind.PARTICIPANT_KIND_SIP:
            await self.session.generate_reply(
                instructions="Hello SIP caller, welcome to the telephony system."
            )
        else:
            await self.session.generate_reply(
                instructions="Hello WebRTC user, welcome to the application."
            )
Why this fixes the error • The agent framework calls
on_enter()
with no participant argument. Making
on_enter(self)
matches the framework’s call. •
self.session.room_io.linked_participant
is where the framework exposes the participant associated with the session, so you can access the participant inside the handler. If you want, I can: • Show how to handle cases where
linked_participant
might be populated slightly later (retry loop or wait/timeout). • Adapt the example to include logging or inspect SIP-specific fields like
participant.sip.phoneNumber
if available. Would you like the retry/wait pattern example or logging added to this handler? Sources: Agents and handoffs | LiveKit Documentation | Agent session | LiveKit Documentation | SIP Lifecycle Management Agent | LiveKit Documentation