Skip to content

Surface SIP hold/resume (re-INVITE media direction) as a participant attribute #716

@vlxdisluv

Description

@vlxdisluv

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)

  1. Attribute name/format — sip.hold boolean, or a new CallStatus value, or
    a dedicated SDK field? Prefer to follow existing conventions.
  2. Should this be gated behind a feature flag, or on by default?
  3. 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


Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Type

    No type
    No fields configured for issues without a type.

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions