The Pi journal showed 170 000 'upstream silent NxN' messages within one
second — i.e. ReceiveJsonAsync was returning null in zero time, so the
upstream WS to OpenAI was closed. The relay was spinning until idle
watchdog (30 s) fired. Add IRealtimeUpstream.IsClosed + CloseReason,
populated by OpenAIRealtimeUpstream from the close status / WS exception,
and have PumpAsync stop on close and forward
error code=upstream_closed message=close status=<X> desc=<Y>
to the device. Closes the spin AND gives us the OpenAI close reason.
The previous run showed session_started, then total silence from the
relay for the entire conversation — no events forwarded, no errors,
nothing. Two changes to disambiguate:
1. Drop the explicit audio.input.format / audio.output.format objects.
24 kHz PCM16 mono is the documented GA default for both directions
and the only thing OpenAI ever advertised; an unrecognised value
inside the format object is the most likely reason OpenAI silently
ignores incoming audio.
2. Forward 'upstream silent NxN250ms' debug envelopes every ~2 s when
ReceiveJsonAsync keeps returning null, and add a 'server_version'
field to hello_ack so the Pi journal proves which build is running.
Upstream returned 'beta_api_shape_disabled' on every wake. Changes:
- Drop OpenAI-Beta: realtime=v1 header from the upgrade.
- session.update payload nests audio under session.audio.{input,output}
and adds session.type='realtime'; audio.{input,output}.format is now
an object {type:audio/pcm, rate:24000} instead of 'pcm16' string.
- Default model bumped to gpt-realtime (the GA name); the relay also
promotes any stored 'gpt-4o-realtime-preview*' to gpt-realtime so
pre-Plan-3 DeviceConfig rows keep working without a manual DB edit.