fix(realtime): raise WebRTC data-channel max-message-size for large events

Browsers advertise a conservative SCTP max-message-size in their SDP offer
(Chrome uses 256 KiB). pion enforces the remote's advertised value on send, so
a single realtime event larger than it cannot be sent over the "oai-events"
data channel: SendText fails, the event is dropped, and the turn silently
yields no response. Some turns legitimately produce a >256 KiB JSON event —
notably tool calls with sizeable schemas or results.

Browsers advertise the value conservatively but their SCTP stacks reassemble
much larger messages, so raise the max-message-size honored for our own
server-generated events by rewriting the attribute in the offer before
SetRemoteDescription.

Assisted-by: Claude:claude-opus-4-8 [Claude Code]
Signed-off-by: Ettore Di Giacinto <mudler@localai.io>
This commit is contained in:
Ettore Di Giacinto
2026-06-19 16:20:39 +00:00
parent 606128e4e9
commit 0c72c486ca
3 changed files with 67 additions and 2 deletions

View File

@@ -128,10 +128,13 @@ func RealtimeCalls(application *application.Application) echo.HandlerFunc {
handleIncomingAudioTrack(track, transport)
})
// Set the remote SDP (client's offer)
// Set the remote SDP (client's offer). Raise the data-channel
// max-message-size the browser advertised so pion permits the larger
// realtime events some turns produce (e.g. tool calls), which would
// otherwise be dropped on send. See realtime_webrtc_sctp.go.
if err := pc.SetRemoteDescription(webrtc.SessionDescription{
Type: webrtc.SDPTypeOffer,
SDP: req.SDP,
SDP: raiseDataChannelMaxMessageSize(req.SDP),
}); err != nil {
transport.Close()
xlog.Error("failed to set remote description", "error", err)

View File

@@ -0,0 +1,29 @@
package openai
import (
"fmt"
"regexp"
)
// realtimeDataChannelMaxMessageSize is the SCTP max-message-size LocalAI honors
// for the "oai-events" data channel, in bytes.
//
// Browsers advertise a conservative max-message-size in their SDP offer (Chrome
// uses 262144 = 256 KiB). pion enforces the remote's advertised value on send,
// so a single realtime event larger than it cannot be sent: the SendText fails,
// the event is dropped, and the turn silently yields no response. Some turns
// legitimately produce a single JSON event above 256 KiB (notably tool calls
// with sizeable schemas or results). Browsers advertise this value
// conservatively but their SCTP stacks reassemble much larger messages, so we
// raise the value honored for our own server-generated events.
const realtimeDataChannelMaxMessageSize = 16 * 1024 * 1024 // 16 MiB
var maxMessageSizeAttrRe = regexp.MustCompile(`a=max-message-size:\d+`)
// raiseDataChannelMaxMessageSize rewrites the SCTP max-message-size attribute in
// an SDP offer to realtimeDataChannelMaxMessageSize so pion permits larger
// outbound realtime events. Offers that don't carry the attribute are returned
// unchanged.
func raiseDataChannelMaxMessageSize(sdp string) string {
return maxMessageSizeAttrRe.ReplaceAllString(sdp, fmt.Sprintf("a=max-message-size:%d", realtimeDataChannelMaxMessageSize))
}

View File

@@ -0,0 +1,33 @@
package openai
import (
"fmt"
"strings"
. "github.com/onsi/ginkgo/v2"
. "github.com/onsi/gomega"
)
var _ = Describe("raiseDataChannelMaxMessageSize", func() {
It("raises a max-message-size the browser advertised", func() {
offer := "v=0\r\nm=application 9 UDP/DTLS/SCTP webrtc-datachannel\r\na=max-message-size:262144\r\n"
out := raiseDataChannelMaxMessageSize(offer)
Expect(out).To(ContainSubstring(fmt.Sprintf("a=max-message-size:%d", realtimeDataChannelMaxMessageSize)))
Expect(out).NotTo(ContainSubstring("a=max-message-size:262144"))
})
It("leaves an offer without the attribute unchanged", func() {
offer := "v=0\r\nm=application 9 UDP/DTLS/SCTP webrtc-datachannel\r\n"
Expect(raiseDataChannelMaxMessageSize(offer)).To(Equal(offer))
})
It("rewrites every occurrence", func() {
offer := "a=max-message-size:1024\r\na=max-message-size:262144\r\n"
out := raiseDataChannelMaxMessageSize(offer)
Expect(strings.Count(out, fmt.Sprintf("a=max-message-size:%d", realtimeDataChannelMaxMessageSize))).To(Equal(2))
})
It("raises above the 256 KiB browsers advertise", func() {
Expect(realtimeDataChannelMaxMessageSize).To(BeNumerically(">", 262144))
})
})