@@ -234,7 +234,7 @@ export class RealtimeModel extends llm.RealtimeModel {
234234 *
235235 * @param azureDeployment - The name of your Azure OpenAI deployment.
236236 * @param azureEndpoint - The endpoint URL for your Azure OpenAI resource. If undefined, will attempt to read from the environment variable AZURE_OPENAI_ENDPOINT.
237- * @param apiVersion - API version to use with Azure OpenAI Service. If undefined, will attempt to read from the environment variable OPENAI_API_VERSION .
237+ * @param apiVersion - **Deprecated.** API version for legacy Azure OpenAI preview models. Will be removed on April 30, 2026. Omit for GA models .
238238 * @param apiKey - Azure OpenAI API key. If undefined, will attempt to read from the environment variable AZURE_OPENAI_API_KEY.
239239 * @param entraToken - Azure Entra authentication token. Required if not using API key authentication.
240240 * @param baseURL - Base URL for the API endpoint. If undefined, constructed from the azure_endpoint.
@@ -287,9 +287,10 @@ export class RealtimeModel extends llm.RealtimeModel {
287287 }
288288
289289 apiVersion = apiVersion || process . env . OPENAI_API_VERSION ;
290- if ( ! apiVersion ) {
291- throw new Error (
292- 'Must provide either the `apiVersion` argument or the `OPENAI_API_VERSION` environment variable' ,
290+ if ( apiVersion ) {
291+ log ( ) . warn (
292+ 'The `apiVersion` parameter for Azure OpenAI Realtime is deprecated and will be removed on April 30, 2026. ' +
293+ 'Please use the newer Azure OpenAI Realtime API without specifying an API version.' ,
293294 ) ;
294295 }
295296
@@ -327,6 +328,24 @@ export class RealtimeModel extends llm.RealtimeModel {
327328 }
328329}
329330
331+ /**
332+ * In-place normalization of client event dicts for legacy Azure compatibility.
333+ *
334+ * The legacy Azure Realtime API uses "text" for assistant content parts,
335+ * while the newer OpenAI API uses "output_text".
336+ */
337+ function normalizeAzureClientEvent ( event : Record < string , unknown > ) : void {
338+ const item = event [ 'item' ] as Record < string , unknown > | undefined ;
339+ if ( ! item ) return ;
340+ const content = item [ 'content' ] as Record < string , unknown > [ ] | undefined ;
341+ if ( ! content ) return ;
342+ for ( const contentPart of content ) {
343+ if ( contentPart [ 'type' ] === 'output_text' ) {
344+ contentPart [ 'type' ] = 'text' ;
345+ }
346+ }
347+ }
348+
330349function processBaseURL ( {
331350 baseURL,
332351 model,
@@ -340,7 +359,9 @@ function processBaseURL({
340359 azureDeployment ?: string ;
341360 apiVersion ?: string ;
342361} ) : string {
343- const url = new URL ( [ baseURL , 'realtime' ] . join ( '/' ) ) ;
362+ // Azure GA (no apiVersion) uses /v1/realtime; legacy preview uses /realtime
363+ const realtimePath = isAzure && ! apiVersion ? 'v1/realtime' : 'realtime' ;
364+ const url = new URL ( [ baseURL , realtimePath ] . join ( '/' ) ) ;
344365
345366 if ( url . protocol === 'https:' ) {
346367 url . protocol = 'wss:' ;
@@ -354,14 +375,19 @@ function processBaseURL({
354375 }
355376
356377 const queryParams : Record < string , string > = { } ;
357- if ( isAzure ) {
358- if ( apiVersion ) {
359- queryParams [ 'api-version' ] = apiVersion ;
360- }
378+ if ( isAzure && apiVersion ) {
379+ // Legacy Azure preview: /realtime?api-version=<v>&deployment=<d>
380+ queryParams [ 'api-version' ] = apiVersion ;
361381 if ( azureDeployment ) {
362382 queryParams [ 'deployment' ] = azureDeployment ;
363383 }
384+ } else if ( isAzure ) {
385+ // GA Azure: /v1/realtime?model=<deployment>
386+ if ( azureDeployment ) {
387+ queryParams [ 'model' ] = azureDeployment ;
388+ }
364389 } else {
390+ // Standard OpenAI: /realtime?model=<model>
365391 queryParams [ 'model' ] = model ;
366392 }
367393
@@ -434,37 +460,58 @@ export class RealtimeSession extends llm.RealtimeSession {
434460 }
435461
436462 private createSessionUpdateEvent ( ) : api_proto . SessionUpdateEvent {
437- const audioFormat : api_proto . AudioFormat = { type : 'audio/pcm' , rate : SAMPLE_RATE } ;
438-
439- const modality : Modality = this . oaiRealtimeModel . _options . modalities . includes ( 'audio' )
440- ? 'audio'
441- : 'text' ;
463+ const opts = this . oaiRealtimeModel . _options ;
464+ const maxOutputTokens =
465+ opts . maxResponseOutputTokens === Infinity ? 'inf' : opts . maxResponseOutputTokens ;
466+
467+ if ( opts . isAzure && opts . apiVersion ) {
468+ // Legacy Azure preview API: flat format
469+ const modalities : Modality [ ] = opts . modalities . includes ( 'audio' )
470+ ? [ 'text' , 'audio' ]
471+ : [ 'text' ] ;
472+ return {
473+ type : 'session.update' ,
474+ session : {
475+ model : opts . model ,
476+ voice : opts . voice ,
477+ input_audio_format : 'pcm16' ,
478+ output_audio_format : 'pcm16' ,
479+ modalities,
480+ turn_detection : opts . turnDetection ,
481+ input_audio_transcription : opts . inputAudioTranscription ,
482+ tool_choice : toOaiToolChoice ( opts . toolChoice ) ,
483+ max_response_output_tokens : maxOutputTokens ,
484+ speed : opts . speed ,
485+ instructions : this . instructions ,
486+ } ,
487+ } ;
488+ }
442489
490+ // GA format (OpenAI or Azure GA)
491+ const audioFormat : api_proto . AudioFormat = { type : 'audio/pcm' , rate : SAMPLE_RATE } ;
492+ const modality : Modality = opts . modalities . includes ( 'audio' ) ? 'audio' : 'text' ;
443493 return {
444494 type : 'session.update' ,
445495 session : {
446496 type : 'realtime' ,
447- model : this . oaiRealtimeModel . _options . model ,
497+ model : opts . model ,
448498 output_modalities : [ modality ] ,
449499 audio : {
450500 input : {
451501 format : audioFormat ,
452- noise_reduction : this . oaiRealtimeModel . _options . inputAudioNoiseReduction ,
453- transcription : this . oaiRealtimeModel . _options . inputAudioTranscription ,
454- turn_detection : this . oaiRealtimeModel . _options . turnDetection ,
502+ noise_reduction : opts . inputAudioNoiseReduction ,
503+ transcription : opts . inputAudioTranscription ,
504+ turn_detection : opts . turnDetection ,
455505 } ,
456506 output : {
457507 format : audioFormat ,
458- speed : this . oaiRealtimeModel . _options . speed ,
459- voice : this . oaiRealtimeModel . _options . voice ,
508+ speed : opts . speed ,
509+ voice : opts . voice ,
460510 } ,
461511 } ,
462- max_output_tokens :
463- this . oaiRealtimeModel . _options . maxResponseOutputTokens === Infinity
464- ? 'inf'
465- : this . oaiRealtimeModel . _options . maxResponseOutputTokens ,
466- tool_choice : toOaiToolChoice ( this . oaiRealtimeModel . _options . toolChoice ) ,
467- tracing : this . oaiRealtimeModel . _options . tracing ,
512+ max_output_tokens : maxOutputTokens ,
513+ tool_choice : toOaiToolChoice ( opts . toolChoice ) ,
514+ tracing : opts . tracing ,
468515 instructions : this . instructions ,
469516 } ,
470517 } ;
@@ -952,6 +999,9 @@ export class RealtimeSession extends llm.RealtimeSession {
952999 try {
9531000 for ( const ev of events ) {
9541001 this . emit ( 'openai_client_event_queued' , ev ) ;
1002+ if ( this . oaiRealtimeModel . _options . isAzure && this . oaiRealtimeModel . _options . apiVersion ) {
1003+ normalizeAzureClientEvent ( ev as unknown as Record < string , unknown > ) ;
1004+ }
9551005 wsConn ! . send ( JSON . stringify ( ev ) ) ;
9561006 }
9571007 } catch ( error ) {
@@ -1042,6 +1092,9 @@ export class RealtimeSession extends llm.RealtimeSession {
10421092 }
10431093
10441094 this . emit ( 'openai_client_event_queued' , event ) ;
1095+ if ( this . oaiRealtimeModel . _options . isAzure && this . oaiRealtimeModel . _options . apiVersion ) {
1096+ normalizeAzureClientEvent ( event as unknown as Record < string , unknown > ) ;
1097+ }
10451098 wsConn . send ( JSON . stringify ( event ) ) ;
10461099 } catch ( error ) {
10471100 break ;
0 commit comments