@@ -183,6 +183,13 @@ export class RealtimeSession<
183183 #shouldIncludeAudioData: boolean ;
184184 #interruptedByGuardrail: Record < string , boolean > = { } ;
185185 #audioStarted = false ;
186+ // Keeps track of the last full session config we sent (camelCase keys) so that
187+ // subsequent updates (e.g. during agent handoffs) preserve properties that are
188+ // not explicitly recalculated here (such as inputAudioFormat, outputAudioFormat,
189+ // modalities, speed, toolChoice, turnDetection, etc.). Without this, updating
190+ // the agent would drop audio format overrides (e.g. g711_ulaw) and revert to
191+ // transport defaults causing issues for integrations like Twilio.
192+ #lastSessionConfig: Partial < RealtimeSessionConfig > | null = null ;
186193
187194 constructor (
188195 public readonly initialAgent :
@@ -313,14 +320,34 @@ export class RealtimeSession<
313320 ) ;
314321 }
315322
316- return {
323+ // Start from any previously-sent config (so we preserve values like audio formats)
324+ // and the original options.config provided by the user. Preference order:
325+ // 1. Last session config we sent (#lastSessionConfig)
326+ // 2. Original options.config
327+ // 3. Additional config passed into this invocation (explicit overrides)
328+ // Finally we overwrite dynamic fields (instructions, voice, model, tools, tracing)
329+ // to ensure they always reflect the current agent & runtime state.
330+ const base : Partial < RealtimeSessionConfig > = {
331+ ...( this . #lastSessionConfig ?? { } ) ,
332+ ...( this . options . config ?? { } ) ,
333+ ...( additionalConfig ?? { } ) ,
334+ } ;
335+
336+ // Note: Certain fields cannot be updated after the session begins, such as voice and model
337+ const fullConfig : Partial < RealtimeSessionConfig > = {
338+ ...base ,
317339 instructions,
318340 voice : this . #currentAgent. voice ,
319341 model : this . options . model ,
320342 tools : this . #currentTools,
321343 tracing : tracingConfig ,
322- ...additionalConfig ,
323344 } ;
345+
346+ // Update our cache so subsequent updates inherit the full set including any
347+ // dynamic fields we just overwrote.
348+ this . #lastSessionConfig = fullConfig ;
349+
350+ return fullConfig ;
324351 }
325352
326353 async updateAgent ( newAgent : RealtimeAgent < TBaseContext > ) {
@@ -689,6 +716,9 @@ export class RealtimeSession<
689716 url : options . url ,
690717 initialSessionConfig : await this . #getSessionConfig( this . options . config ) ,
691718 } ) ;
719+ // Ensure the cached lastSessionConfig includes everything passed as the initial session config
720+ // (the call above already set it via #getSessionConfig but in case additional overrides were
721+ // passed directly here in the future we could merge them). For now it's a no-op.
692722
693723 this . #history = [ ] ;
694724 this . emit ( 'history_updated' , this . #history) ;
0 commit comments