@@ -208,6 +208,23 @@ export class TelnyxProvider implements VoiceCallProvider {
208208 digits : data . payload ?. digit || "" ,
209209 } ;
210210
211+ case "call.streaming.started" :
212+ case "call.streaming.stopped" :
213+ // Informational. The realtime bridge tracks its own lifecycle via the
214+ // WebSocket; we acknowledge the webhook (200) but skip event emission
215+ // to avoid duplicate signal at the manager.
216+ return null ;
217+
218+ case "call.streaming.failed" : {
219+ const reason = typeof data . payload ?. reason === "string" ? data . payload . reason : undefined ;
220+ return {
221+ ...baseEvent ,
222+ type : "call.error" ,
223+ error : `Telnyx streaming failed${ reason ? `: ${ reason } ` : "" } ` ,
224+ retryable : false ,
225+ } ;
226+ }
227+
211228 default :
212229 return null ;
213230 }
@@ -251,18 +268,24 @@ export class TelnyxProvider implements VoiceCallProvider {
251268 }
252269
253270 /**
254- * Initiate an outbound call via Telnyx API.
271+ * Initiate an outbound call via Telnyx API. When `input.streamUrl` is set,
272+ * the dial payload also opens a bidirectional Media Streaming session on
273+ * answer (PCMU 8 kHz), per the Telnyx documented "AI agent" pattern.
255274 */
256275 async initiateCall ( input : InitiateCallInput ) : Promise < InitiateCallResult > {
257- const result = await this . apiRequest < TelnyxCallResponse > ( "/calls" , {
276+ const body : Record < string , unknown > = {
258277 connection_id : this . connectionId ,
259278 to : input . to ,
260279 from : input . from ,
261280 webhook_url : input . webhookUrl ,
262281 webhook_url_method : "POST" ,
263282 client_state : Buffer . from ( input . callId ) . toString ( "base64" ) ,
264283 timeout_secs : 30 ,
265- } ) ;
284+ ...( input . streamUrl
285+ ? buildTelnyxStreamingFields ( input . streamUrl , input . streamAuthToken )
286+ : { } ) ,
287+ } ;
288+ const result = await this . apiRequest < TelnyxCallResponse > ( "/calls" , body ) ;
266289
267290 return {
268291 providerCallId : result . data . call_control_id ,
@@ -282,12 +305,18 @@ export class TelnyxProvider implements VoiceCallProvider {
282305 }
283306
284307 /**
285- * Answer an inbound Telnyx Call Control leg.
308+ * Answer an inbound Telnyx Call Control leg. When `input.streamUrl` is set,
309+ * the answer action also attaches a bidirectional Media Streaming session
310+ * (PCMU 8 kHz), per the Telnyx canonical "answer-action inline" pattern.
286311 */
287312 async answerCall ( input : AnswerCallInput ) : Promise < void > {
288- await this . apiRequest ( `/calls/ ${ input . providerCallId } /actions/answer` , {
313+ const body : Record < string , unknown > = {
289314 command_id : `openclaw-answer-${ input . callId } ` ,
290- } ) ;
315+ ...( input . streamUrl
316+ ? buildTelnyxStreamingFields ( input . streamUrl , input . streamAuthToken )
317+ : { } ) ,
318+ } ;
319+ await this . apiRequest ( `/calls/${ input . providerCallId } /actions/answer` , body ) ;
291320 }
292321
293322 /**
@@ -355,6 +384,28 @@ export class TelnyxProvider implements VoiceCallProvider {
355384 }
356385}
357386
387+ /**
388+ * Build the streaming-related fields for a Telnyx dial or answer-action
389+ * payload. PCMU 8 kHz mono only; bidirectional via RTP; target legs `"self"`
390+ * so the bot receives both inbound and outbound audio without the routing
391+ * gotcha that drops the call leg when `"opposite"` is configured.
392+ */
393+ function buildTelnyxStreamingFields (
394+ streamUrl : string ,
395+ streamAuthToken : string | undefined ,
396+ ) : Record < string , unknown > {
397+ return {
398+ stream_url : streamUrl ,
399+ stream_track : "inbound_track" ,
400+ stream_codec : "PCMU" ,
401+ stream_bidirectional_mode : "rtp" ,
402+ stream_bidirectional_codec : "PCMU" ,
403+ stream_bidirectional_sampling_rate : 8000 ,
404+ stream_bidirectional_target_legs : "self" ,
405+ ...( streamAuthToken ? { stream_auth_token : streamAuthToken } : { } ) ,
406+ } ;
407+ }
408+
358409// -----------------------------------------------------------------------------
359410// Telnyx-specific types
360411// -----------------------------------------------------------------------------
0 commit comments