Skip to content

Commit ecd562b

Browse files
committed
fix(realtime): label pre-ready transcription closes
1 parent 34b3471 commit ecd562b

6 files changed

Lines changed: 35 additions & 1 deletion

File tree

CHANGELOG.md

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -36,6 +36,7 @@ Docs: https://docs.openclaw.ai
3636

3737
- Channels/WhatsApp: allow `@whiskeysockets/libsignal-node` in `onlyBuiltDependencies` so pnpm v9+ `blockExoticSubdeps` no longer rejects the baileys git-tarball subdep and silences all inbound agent replies. Fixes #76539. Thanks @ottodeng and @vincentkoc.
3838
- Gateway/systemd: preserve operator-added secrets in the Gateway env file across re-stage while clearing OpenClaw-managed keys (such as `OPENCLAW_GATEWAY_TOKEN`) so a fresh staging value is never shadowed by a stale env-file copy; operator secrets are also retained when the state-dir `.env` is empty. Fixes #76860. Thanks @hclsys.
39+
- Realtime transcription: report socket closes before provider readiness as closed-before-ready failures instead of mislabeling them as connection timeouts for OpenAI, xAI, and Deepgram streaming transcription. Thanks @vincentkoc.
3940
- OpenAI/Google Meet: fail realtime voice connection attempts when the socket closes before `session.updated`, avoiding stuck Meet joins waiting on a bridge that never became ready. Thanks @vincentkoc.
4041
- QA/cache: require the full `CACHE-OK <suffix>` marker before live cache probes stop retrying, so suffix-only prose cannot hide a broken probe response. Thanks @vincentkoc.
4142
- Slack/Matrix: avoid creating blank progress-draft messages when `streaming.progress.label=false` and progress tool lines are disabled. Thanks @vincentkoc.

extensions/deepgram/realtime-transcription-provider.ts

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -232,6 +232,8 @@ function createDeepgramRealtimeTranscriptionSession(
232232
reconnectDelayMs: DEEPGRAM_REALTIME_RECONNECT_DELAY_MS,
233233
maxQueuedBytes: DEEPGRAM_REALTIME_MAX_QUEUED_BYTES,
234234
connectTimeoutMessage: "Deepgram realtime transcription connection timeout",
235+
connectClosedBeforeReadyMessage:
236+
"Deepgram realtime transcription connection closed before ready",
235237
reconnectLimitMessage: "Deepgram realtime transcription reconnect limit reached",
236238
sendAudio: (audio, transport) => {
237239
transport.sendBinary(audio);

extensions/openai/realtime-transcription-provider.ts

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -138,6 +138,7 @@ function createOpenAIRealtimeTranscriptionSession(
138138
maxReconnectAttempts: OPENAI_REALTIME_TRANSCRIPTION_MAX_RECONNECT_ATTEMPTS,
139139
reconnectDelayMs: OPENAI_REALTIME_TRANSCRIPTION_RECONNECT_DELAY_MS,
140140
connectTimeoutMessage: "OpenAI realtime transcription connection timeout",
141+
connectClosedBeforeReadyMessage: "OpenAI realtime transcription connection closed before ready",
141142
reconnectLimitMessage: "OpenAI realtime transcription reconnect limit reached",
142143
sendAudio: (audio, transport) => {
143144
transport.sendJson({

extensions/xai/realtime-transcription-provider.ts

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -226,6 +226,7 @@ function createXaiRealtimeTranscriptionSession(
226226
reconnectDelayMs: XAI_REALTIME_STT_RECONNECT_DELAY_MS,
227227
maxQueuedBytes: XAI_REALTIME_STT_MAX_QUEUED_BYTES,
228228
connectTimeoutMessage: "xAI realtime transcription connection timeout",
229+
connectClosedBeforeReadyMessage: "xAI realtime transcription connection closed before ready",
229230
reconnectLimitMessage: "xAI realtime transcription reconnect limit reached",
230231
sendAudio: (audio, transport) => {
231232
transport.sendBinary(audio);

src/realtime-transcription/websocket-session.test.ts

Lines changed: 28 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -13,6 +13,7 @@ afterEach(async () => {
1313
});
1414

1515
async function createRealtimeServer(params?: {
16+
closeOnConnection?: boolean;
1617
initialEvent?: unknown;
1718
onBinary?: (payload: Buffer) => void;
1819
onText?: (payload: unknown) => void;
@@ -25,6 +26,10 @@ async function createRealtimeServer(params?: {
2526
wss.handleUpgrade(request, socket, head, (ws) => {
2627
clients.add(ws);
2728
ws.on("close", () => clients.delete(ws));
29+
if (params?.closeOnConnection) {
30+
ws.close(1011, "setup failed");
31+
return;
32+
}
2833
if (params?.initialEvent) {
2934
ws.send(JSON.stringify(params.initialEvent));
3035
}
@@ -153,4 +158,27 @@ describe("createRealtimeTranscriptionWebSocketSession", () => {
153158
expect(session.isConnected()).toBe(false);
154159
expect(onError).toHaveBeenCalledWith(expect.any(Error));
155160
});
161+
162+
it("reports pre-ready closes separately from connection timeouts", async () => {
163+
const server = await createRealtimeServer({ closeOnConnection: true });
164+
const onError = vi.fn();
165+
const session = createRealtimeTranscriptionWebSocketSession({
166+
providerId: "test",
167+
callbacks: { onError },
168+
url: server.url,
169+
connectTimeoutMessage: "test realtime transcription connection timeout",
170+
connectClosedBeforeReadyMessage: "test realtime transcription connection closed before ready",
171+
sendAudio: (audio, transport) => {
172+
transport.sendBinary(audio);
173+
},
174+
});
175+
176+
await expect(session.connect()).rejects.toThrow(
177+
"test realtime transcription connection closed before ready",
178+
);
179+
expect(onError).toHaveBeenCalledWith(expect.any(Error));
180+
expect(onError.mock.calls[0]?.[0]).toMatchObject({
181+
message: "test realtime transcription connection closed before ready",
182+
});
183+
});
156184
});

src/realtime-transcription/websocket-session.ts

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -20,6 +20,7 @@ export type RealtimeTranscriptionWebSocketTransport = {
2020

2121
export type RealtimeTranscriptionWebSocketSessionOptions<Event = unknown> = {
2222
callbacks: RealtimeTranscriptionSessionCallbacks;
23+
connectClosedBeforeReadyMessage?: string;
2324
connectTimeoutMessage?: string;
2425
connectTimeoutMs?: number;
2526
closeTimeoutMs?: number;
@@ -267,7 +268,7 @@ class WebSocketRealtimeTranscriptionSession<Event> implements RealtimeTranscript
267268
if (!opened || !settled) {
268269
failConnect(
269270
new Error(
270-
this.options.connectTimeoutMessage ??
271+
this.options.connectClosedBeforeReadyMessage ??
271272
`${this.options.providerId} realtime transcription connection closed before ready`,
272273
),
273274
);

0 commit comments

Comments
 (0)