Summary
When the remote party puts a SIP call on hold/resume, livekit-sip currently
does not surface this to the LiveKit room. We'd like the hold state to be
exposed as a participant attribute so that agents (and other room participants)
can react to it.
Motivation
Voice agents need to know when the SIP participant is placed on hold and when
the call is resumed, in order to drive their own logic (e.g. pause/resume
processing, stop talking, emit an event). Today there is no signal for this in
the room, even though SIP carries it explicitly.
Current behavior
Hold/resume in SIP is signaled via an in-dialog re-INVITE (or UPDATE) whose SDP
changes the media direction attribute (a=sendonly / a=recvonly /
a=inactive / a=sendrecv).
In livekit-sip, an in-dialog re-INVITE is currently treated purely as a
keep-alive: the previous SDP is echoed back and the incoming SDP direction is
not inspected. See Server.processInvite (pkg/sip/inbound.go, the re-INVITE
branches that call AcceptAsKeepAlive(...), around lines 382–398) and
sipInbound.AcceptAsKeepAlive (pkg/sip/inbound.go). Generated offers/answers
always emit a=sendrecv (in media-sdk/sdp), and sendonly/recvonly/
inactive are never parsed. As a result, hold is invisible to the room.
Proposal
On an in-dialog re-INVITE for an existing call (both inbound and outbound),
parse the media direction from the incoming SDP and, when it transitions:
sendonly / inactive → mark the participant as on hold
sendrecv (or back to a receiving state) → mark as resumed
Expose this via a participant attribute (e.g. sip.hold = "true"/"false"), set
through the existing LocalParticipant.SetAttributes path already used for
sip.callStatus (CallStatus.Attribute() in pkg/sip/participant.go).
Scope kept intentionally minimal and backwards-compatible:
- Keep responding with the existing echoed SDP (
AcceptAsKeepAlive) and keep
the current CSeq handling — the SIP/SDP answer is not changed.
- Do not change the media-timeout behavior. This PR only adds observability
(a status attribute); call survival semantics stay exactly as today.
- Only flip the attribute on actual state changes, so periodic
session-timer re-INVITEs (sendrecv) don't cause churn.
Open questions (would love maintainer input before a PR)
- Attribute name/format —
sip.hold boolean, or a new CallStatus value, or
a dedicated SDK field? Prefer to follow existing conventions.
- Should this be gated behind a feature flag, or on by default?
- Should
recvonly be treated as hold, or only sendonly/inactive?
Contribution
I'm happy to implement this and open a PR (including tests for
sendonly/inactive → hold and sendrecv → resume, covering both inbound and
outbound paths) once we agree on the attribute shape.
Suggested labels: enhancement, sip / telephony
Summary
When the remote party puts a SIP call on hold/resume, livekit-sip currently
does not surface this to the LiveKit room. We'd like the hold state to be
exposed as a participant attribute so that agents (and other room participants)
can react to it.
Motivation
Voice agents need to know when the SIP participant is placed on hold and when
the call is resumed, in order to drive their own logic (e.g. pause/resume
processing, stop talking, emit an event). Today there is no signal for this in
the room, even though SIP carries it explicitly.
Current behavior
Hold/resume in SIP is signaled via an in-dialog re-INVITE (or UPDATE) whose SDP
changes the media direction attribute (
a=sendonly/a=recvonly/a=inactive/a=sendrecv).In livekit-sip, an in-dialog re-INVITE is currently treated purely as a
keep-alive: the previous SDP is echoed back and the incoming SDP direction is
not inspected. See
Server.processInvite(pkg/sip/inbound.go, the re-INVITEbranches that call
AcceptAsKeepAlive(...), around lines 382–398) andsipInbound.AcceptAsKeepAlive(pkg/sip/inbound.go). Generated offers/answersalways emit
a=sendrecv(inmedia-sdk/sdp), andsendonly/recvonly/inactiveare never parsed. As a result, hold is invisible to the room.Proposal
On an in-dialog re-INVITE for an existing call (both inbound and outbound),
parse the media direction from the incoming SDP and, when it transitions:
sendonly/inactive→ mark the participant as on holdsendrecv(or back to a receiving state) → mark as resumedExpose this via a participant attribute (e.g.
sip.hold = "true"/"false"), setthrough the existing
LocalParticipant.SetAttributespath already used forsip.callStatus(CallStatus.Attribute()inpkg/sip/participant.go).Scope kept intentionally minimal and backwards-compatible:
AcceptAsKeepAlive) and keepthe current CSeq handling — the SIP/SDP answer is not changed.
(a status attribute); call survival semantics stay exactly as today.
session-timer re-INVITEs (
sendrecv) don't cause churn.Open questions (would love maintainer input before a PR)
sip.holdboolean, or a newCallStatusvalue, ora dedicated SDK field? Prefer to follow existing conventions.
recvonlybe treated as hold, or onlysendonly/inactive?Contribution
I'm happy to implement this and open a PR (including tests for
sendonly/inactive→ hold andsendrecv→ resume, covering both inbound andoutbound paths) once we agree on the attribute shape.
Suggested labels:
enhancement,sip/telephony