From fa139b4fcadc8f159c8e74fa94948215ac9b6f66 Mon Sep 17 00:00:00 2001 From: Peter Steinberger Date: Fri, 24 Apr 2026 07:25:38 +0100 Subject: [PATCH] fix(voice-call): handle Telnyx callback payloads --- CHANGELOG.md | 1 + .../voice-call/src/providers/telnyx.test.ts | 70 +++++++++++++++++++ extensions/voice-call/src/providers/telnyx.ts | 33 ++++++++- 3 files changed, 101 insertions(+), 3 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 56d6ede72b7..acb13e8ae6d 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -27,6 +27,7 @@ Docs: https://docs.openclaw.ai ### Fixes +- Voice-call/Telnyx: preserve inbound/outbound callback metadata and read transcription text from Telnyx's current `transcription_data` payload. - Codex harness: route native `request_user_input` prompts back to the originating chat, preserve queued follow-up answers, and honor newer app-server command approval amendment decisions. - Codex status: report Codex CLI OAuth as `oauth (codex-cli)` for native `codex/*` sessions instead of showing unknown auth. Fixes #70688. Thanks @jb510. - Codex harness/context-engine: redact context-engine assembly failures before logging, so fallback warnings do not serialize raw error objects. (#70809) Thanks @jalehman. diff --git a/extensions/voice-call/src/providers/telnyx.test.ts b/extensions/voice-call/src/providers/telnyx.test.ts index 336d385711c..48ddeed852b 100644 --- a/extensions/voice-call/src/providers/telnyx.test.ts +++ b/extensions/voice-call/src/providers/telnyx.test.ts @@ -185,4 +185,74 @@ describe("TelnyxProvider.parseWebhookEvent", () => { } expect(event.dedupeKey).toBe("telnyx:req:abc"); }); + + it("maps call direction and phone numbers from Call Control callbacks", () => { + const provider = new TelnyxProvider({ + apiKey: "KEY123", + connectionId: "CONN456", + publicKey: undefined, + }); + const result = provider.parseWebhookEvent( + createCtx({ + rawBody: JSON.stringify({ + data: { + id: "evt-inbound", + event_type: "call.initiated", + payload: { + call_control_id: "call-1", + direction: "incoming", + from: "+15551111111", + to: "+15550000000", + }, + }, + }), + }), + ); + + expect(result.events).toHaveLength(1); + expect(result.events[0]).toEqual( + expect.objectContaining({ + type: "call.initiated", + direction: "inbound", + from: "+15551111111", + to: "+15550000000", + }), + ); + }); + + it("reads transcription text from Telnyx transcription_data payloads", () => { + const provider = new TelnyxProvider({ + apiKey: "KEY123", + connectionId: "CONN456", + publicKey: undefined, + }); + const result = provider.parseWebhookEvent( + createCtx({ + rawBody: JSON.stringify({ + data: { + id: "evt-transcription", + event_type: "call.transcription", + payload: { + call_control_id: "call-1", + transcription_data: { + transcript: "hello this is a test speech", + is_final: false, + confidence: 0.977219, + }, + }, + }, + }), + }), + ); + + expect(result.events).toHaveLength(1); + expect(result.events[0]).toEqual( + expect.objectContaining({ + type: "call.speech", + transcript: "hello this is a test speech", + isFinal: false, + confidence: 0.977219, + }), + ); + }); }); diff --git a/extensions/voice-call/src/providers/telnyx.ts b/extensions/voice-call/src/providers/telnyx.ts index 1ba53457c69..427c749292a 100644 --- a/extensions/voice-call/src/providers/telnyx.ts +++ b/extensions/voice-call/src/providers/telnyx.ts @@ -31,6 +31,21 @@ export interface TelnyxProviderOptions { skipVerification?: boolean; } +function normalizeTelnyxDirection( + direction: string | undefined, +): "inbound" | "outbound" | undefined { + switch (direction) { + case "incoming": + case "inbound": + return "inbound"; + case "outgoing": + case "outbound": + return "outbound"; + default: + return undefined; + } +} + export class TelnyxProvider implements VoiceCallProvider { readonly name = "telnyx" as const; @@ -143,6 +158,9 @@ export class TelnyxProvider implements VoiceCallProvider { callId, providerCallId: data.payload?.call_control_id, timestamp: Date.now(), + direction: normalizeTelnyxDirection(data.payload?.direction), + from: data.payload?.from, + to: data.payload?.to, }; switch (data.event_type) { @@ -169,9 +187,10 @@ export class TelnyxProvider implements VoiceCallProvider { return { ...baseEvent, type: "call.speech", - transcript: data.payload?.transcription || "", - isFinal: data.payload?.is_final ?? true, - confidence: data.payload?.confidence, + transcript: + data.payload?.transcription_data?.transcript ?? data.payload?.transcription ?? "", + isFinal: data.payload?.transcription_data?.is_final ?? data.payload?.is_final ?? true, + confidence: data.payload?.transcription_data?.confidence ?? data.payload?.confidence, }; case "call.hangup": @@ -336,10 +355,18 @@ interface TelnyxEvent { payload?: { call_control_id?: string; client_state?: string; + direction?: string; + from?: string; + to?: string; text?: string; transcription?: string; is_final?: boolean; confidence?: number; + transcription_data?: { + transcript?: string; + is_final?: boolean; + confidence?: number; + }; hangup_cause?: string; digit?: string; [key: string]: unknown;