fix(media-server): fall back to sole agent peer when peer_id missing

Rails' /agent_answer forwards only the SDP answer to the media server; it
doesn't persist or echo the peer_id returned from /agent-offer. The
AgentAnswer handler required peer_id and 400'd on every Rails call, so
Peer B never completed its handshake. With no agent remote track, the
bridge delivered nothing to Meta, and Meta tore the call down after ~20s
with error 138021 ("WhatsApp client terminated the call due to not
receiving any media").

Add Session.SoleAgentPeerID and have the handler use it when peer_id is
absent. Single-agent sessions now complete negotiation without extra
Rails plumbing. Multi-agent sessions still require an explicit peer_id.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
This commit is contained in:
Tanmay Deep Sharma
2026-04-21 17:50:12 +07:00
co-authored by Claude Opus 4.7
parent 83f32cddac
commit 02279d4e15
2 changed files with 25 additions and 2 deletions
@@ -297,9 +297,16 @@ func (h *Handlers) AgentAnswer(w http.ResponseWriter, r *http.Request) {
writeError(w, http.StatusBadRequest, "sdp_answer is required")
return
}
// When peer_id is omitted (single-agent sessions), fall back to the only
// agent peer attached to the session. Rails doesn't currently surface
// peer_id through ActionCable, so browsers just send the SDP answer.
if req.PeerID == "" {
writeError(w, http.StatusBadRequest, "peer_id is required")
return
if only, ok := sess.SoleAgentPeerID(); ok {
req.PeerID = only
} else {
writeError(w, http.StatusBadRequest, "peer_id is required (session has multiple agent peers)")
return
}
}
if err := sess.SetAgentAnswer(req.PeerID, req.SDPAnswer); err != nil {
@@ -204,6 +204,22 @@ func (s *Session) CreateAgentPeer(peerID string, role peer.PeerRole, iceServers
return sdpOffer, nil
}
// SoleAgentPeerID returns the peer_id when the session has exactly one agent
// peer. Used by the agent-answer handler as a fallback for clients that don't
// track peer ids.
func (s *Session) SoleAgentPeerID() (string, bool) {
s.mu.Lock()
defer s.mu.Unlock()
if len(s.AgentPeers) != 1 {
return "", false
}
for id := range s.AgentPeers {
return id, true
}
return "", false
}
// SetAgentAnswer sets the agent browser's SDP answer on the specified agent
// peer, completing the WebRTC handshake.
func (s *Session) SetAgentAnswer(peerID, sdpAnswer string) error {