@@ -20,6 +20,7 @@ const MOBILE_BARGE_IN_RMS_THRESHOLD = 0.055;
2020const MOBILE_BARGE_IN_PEAK_THRESHOLD = 0.16 ;
2121const MOBILE_BARGE_IN_FRAMES = 4 ;
2222const MOBILE_BARGE_IN_GRACE_MS = 750 ;
23+ const REALTIME_VOICE_CONTEXT_LIMIT = 8 ;
2324
2425export interface RealtimeBargeInProfile {
2526 rmsThreshold : number ;
@@ -63,6 +64,13 @@ export interface RealtimeGatewayRelayCallbacks {
6364 onError ?: ( message : string ) => void ;
6465}
6566
67+ interface RealtimeVoiceContextEntry {
68+ role : "user" | "assistant" ;
69+ text : string ;
70+ }
71+
72+ const realtimeVoiceContextBySession = new Map < string , RealtimeVoiceContextEntry [ ] > ( ) ;
73+
6674type GatewayRelayEvent =
6775 | { relaySessionId ?: string ; type ?: "ready" }
6876 | { relaySessionId ?: string ; type ?: "audio" ; audioBase64 ?: string }
@@ -232,11 +240,13 @@ export class RealtimeGatewayRelaySession {
232240 case "transcript" :
233241 if ( event . role && event . text ) {
234242 if ( event . role === "assistant" && this . pendingToolCalls > 0 ) return ;
235- this . callbacks . onTranscript ?. ( {
243+ const transcript = {
236244 role : event . role ,
237245 text : event . text ,
238246 final : event . final ?? false ,
239- } ) ;
247+ } ;
248+ recordRealtimeVoiceContext ( this . session . sessionKey , transcript ) ;
249+ this . callbacks . onTranscript ?.( transcript ) ;
240250 }
241251 return ;
242252 case "toolCall" :
@@ -310,9 +320,17 @@ export class RealtimeGatewayRelaySession {
310320 sessionKey,
311321 callId,
312322 name,
313- args : withRealtimeScreenContext ( event . args ?? { } ) ,
323+ args : withRealtimeScreenContext (
324+ event . args ?? { } ,
325+ buildRealtimeVoiceContext ( this . session . sessionKey ) ,
326+ ) ,
314327 } ) ;
315328 if ( result . finalText ?. trim ( ) ) {
329+ recordRealtimeVoiceContext ( this . session . sessionKey , {
330+ role : "assistant" ,
331+ text : result . finalText ,
332+ final : true ,
333+ } ) ;
316334 this . callbacks . onTranscript ?.( {
317335 role : "assistant" ,
318336 text : result . finalText . trim ( ) ,
@@ -358,20 +376,55 @@ export class RealtimeGatewayRelaySession {
358376 }
359377}
360378
361- export function withRealtimeScreenContext ( args : unknown ) {
379+ export function withRealtimeScreenContext ( args : unknown , voiceContext ?: string | null ) {
362380 const screenContext = formatPageContextForPrompt ( buildCurrentPageContextForRealtime ( ) ) ;
363- if ( ! screenContext ) return args ;
381+ const realtimeVoiceContext = typeof voiceContext === "string" && voiceContext . trim ( )
382+ ? voiceContext . trim ( )
383+ : null ;
384+ if ( ! screenContext && ! realtimeVoiceContext ) return args ;
364385
365386 const normalized = normalizeRealtimeToolArgs ( args ) ;
366387 const existingContext = typeof normalized . context === "string" && normalized . context . trim ( )
367388 ? normalized . context . trim ( )
368389 : null ;
369390 return {
370391 ...normalized ,
371- context : [ existingContext , screenContext ] . filter ( Boolean ) . join ( "\n\n" ) ,
392+ context : [ existingContext , screenContext , realtimeVoiceContext ] . filter ( Boolean ) . join ( "\n\n" ) ,
372393 } ;
373394}
374395
396+ export function recordRealtimeVoiceContext (
397+ sessionKey : string | undefined ,
398+ event : { role : "user" | "assistant" ; text : string ; final : boolean } ,
399+ ) {
400+ if ( ! event . final ) return ;
401+ const key = normalizeRealtimeSessionKey ( sessionKey ) ;
402+ const text = event . text . trim ( ) ;
403+ if ( ! text ) return ;
404+
405+ const entries = realtimeVoiceContextBySession . get ( key ) ?? [ ] ;
406+ entries . push ( { role : event . role , text } ) ;
407+ realtimeVoiceContextBySession . set ( key , entries . slice ( - REALTIME_VOICE_CONTEXT_LIMIT ) ) ;
408+ }
409+
410+ export function buildRealtimeVoiceContext ( sessionKey : string | undefined ) {
411+ const entries = realtimeVoiceContextBySession . get ( normalizeRealtimeSessionKey ( sessionKey ) ) ;
412+ if ( ! entries ?. length ) return null ;
413+
414+ return [
415+ "Recent realtime voice context for this CrewCMD session:" ,
416+ ...entries . map ( ( entry ) => `${ entry . role } : ${ entry . text } ` ) ,
417+ ] . join ( "\n" ) ;
418+ }
419+
420+ export function clearRealtimeVoiceContextForTest ( ) {
421+ realtimeVoiceContextBySession . clear ( ) ;
422+ }
423+
424+ function normalizeRealtimeSessionKey ( sessionKey : string | undefined ) {
425+ return typeof sessionKey === "string" && sessionKey . trim ( ) ? sessionKey . trim ( ) : "main" ;
426+ }
427+
375428function normalizeRealtimeToolArgs ( args : unknown ) : Record < string , unknown > {
376429 if ( args && typeof args === "object" && ! Array . isArray ( args ) ) return { ...args as Record < string , unknown > } ;
377430 if ( typeof args === "string" ) {
0 commit comments