@ -164,12 +164,14 @@ export class RealtimeSession {
// Typed text is always already a complete string — unlike the voice
// Typed text is always already a complete string — unlike the voice
// path, no buffering is needed.
// path, no buffering is needed.
this . logger . info ( 'messages' , 'User message' , { role : 'user' , text : event.text } ) ;
this . logger . info ( 'messages' , 'User message' , { role : 'user' , text : event.text } ) ;
this . recordAnalytics ( 'message' , ( ) = > {
this . recorder . messageLogged ( {
this . recorder . messageLogged ( {
sessionId : this.sessionId ! ,
sessionId : this.sessionId ! ,
role : 'user' ,
role : 'user' ,
text : event.text ,
text : event.text ,
loggedAt : Date.now ( )
loggedAt : Date.now ( )
} ) ;
} ) ;
} ) ;
}
}
this . providerSession . send ( event ) ;
this . providerSession . send ( event ) ;
}
}
@ -259,12 +261,14 @@ export class RealtimeSession {
role : 'system' ,
role : 'system' ,
text : config.instructions
text : config.instructions
} ) ;
} ) ;
this . recordAnalytics ( 'session start' , ( ) = > {
this . recorder . sessionStarted ( {
this . recorder . sessionStarted ( {
sessionId ,
sessionId ,
model : config.model ,
model : config.model ,
voice : config.voice ,
voice : config.voice ,
startedAt : Date.now ( )
startedAt : Date.now ( )
} ) ;
} ) ;
} ) ;
// Recorded above before wiring onEvent, so there's no window where a
// Recorded above before wiring onEvent, so there's no window where a
// provider event could reach handleProviderEvent before sessionId is set.
// provider event could reach handleProviderEvent before sessionId is set.
this . providerSession . onEvent ( ( event ) = > this . handleProviderEvent ( event ) ) ;
this . providerSession . onEvent ( ( event ) = > this . handleProviderEvent ( event ) ) ;
@ -287,12 +291,14 @@ export class RealtimeSession {
// immediately rather than buffering. Rests on unverified live
// immediately rather than buffering. Rests on unverified live
// behavior; see docs/ANALYTICS.md.
// behavior; see docs/ANALYTICS.md.
this . logger . info ( 'messages' , 'User message' , { role : 'user' , text : event.text } ) ;
this . logger . info ( 'messages' , 'User message' , { role : 'user' , text : event.text } ) ;
this . recordAnalytics ( 'message' , ( ) = > {
this . recorder . messageLogged ( {
this . recorder . messageLogged ( {
sessionId : this.sessionId ! ,
sessionId : this.sessionId ! ,
role : 'user' ,
role : 'user' ,
text : event.text ,
text : event.text ,
loggedAt : Date.now ( )
loggedAt : Date.now ( )
} ) ;
} ) ;
} ) ;
}
}
this . sendToBrowser ( event ) ;
this . sendToBrowser ( event ) ;
return ;
return ;
@ -301,12 +307,14 @@ export class RealtimeSession {
const text = this . assistantTranscriptBuffer ;
const text = this . assistantTranscriptBuffer ;
this . assistantTranscriptBuffer = '' ;
this . assistantTranscriptBuffer = '' ;
this . logger . info ( 'messages' , 'Assistant message' , { role : 'assistant' , text } ) ;
this . logger . info ( 'messages' , 'Assistant message' , { role : 'assistant' , text } ) ;
this . recordAnalytics ( 'message' , ( ) = > {
this . recorder . messageLogged ( {
this . recorder . messageLogged ( {
sessionId : this.sessionId ! ,
sessionId : this.sessionId ! ,
role : 'assistant' ,
role : 'assistant' ,
text ,
text ,
loggedAt : Date.now ( )
loggedAt : Date.now ( )
} ) ;
} ) ;
} ) ;
this . sendToBrowser ( event ) ;
this . sendToBrowser ( event ) ;
return ;
return ;
}
}
@ -391,6 +399,7 @@ export class RealtimeSession {
callId ,
callId ,
durationMs : Date.now ( ) - meta . startedAt
durationMs : Date.now ( ) - meta . startedAt
} ) ;
} ) ;
this . recordAnalytics ( 'tool call' , ( ) = > {
this . recorder . toolCallLogged ( {
this . recorder . toolCallLogged ( {
sessionId : this.sessionId ! ,
sessionId : this.sessionId ! ,
callId ,
callId ,
@ -400,6 +409,7 @@ export class RealtimeSession {
durationMs : Date.now ( ) - meta . startedAt ,
durationMs : Date.now ( ) - meta . startedAt ,
loggedAt : Date.now ( )
loggedAt : Date.now ( )
} ) ;
} ) ;
} ) ;
}
}
this . outstandingToolCalls -- ;
this . outstandingToolCalls -- ;
if ( this . outstandingToolCalls === 0 ) {
if ( this . outstandingToolCalls === 0 ) {
@ -423,7 +433,13 @@ export class RealtimeSession {
// Guarded: sessionId is still null if the browser disconnected
// Guarded: sessionId is still null if the browser disconnected
// mid-handshake, before start() ever reached 'active' — no sessions
// mid-handshake, before start() ever reached 'active' — no sessions
// row exists yet to update in that case.
// row exists yet to update in that case.
this . recorder . sessionEnded ( { sessionId : this.sessionId , endedAt : Date.now ( ) , reason } ) ;
this . recordAnalytics ( 'session end' , ( ) = > {
this . recorder . sessionEnded ( {
sessionId : this.sessionId ! ,
endedAt : Date.now ( ) ,
reason
} ) ;
} ) ;
}
}
}
}
@ -432,17 +448,42 @@ export class RealtimeSession {
// Logged before the delivery guard below: the event "happened" from
// Logged before the delivery guard below: the event "happened" from
// the session's point of view even if the browser socket is already
// the session's point of view even if the browser socket is already
// dead by the time we try to deliver it.
// dead by the time we try to deliver it.
this . recordAnalytics ( 'event' , ( ) = > {
this . recorder . eventLogged ( {
this . recorder . eventLogged ( {
sessionId : this.sessionId ,
sessionId : this.sessionId ,
type : event . type ,
type : event . type ,
detail : JSON.stringify ( event ) ,
detail : JSON.stringify ( event ) ,
loggedAt : Date.now ( )
loggedAt : Date.now ( )
} ) ;
} ) ;
} ) ;
}
}
if ( this . browserSocket . readyState !== WebSocket . OPEN ) return ;
if ( this . browserSocket . readyState !== WebSocket . OPEN ) return ;
this . browserSocket . send ( JSON . stringify ( event ) ) ;
this . browserSocket . send ( JSON . stringify ( event ) ) ;
}
}
/ * *
* Analytics is best - effort telemetry , never part of session correctness .
* A full disk , locked database , or broken recorder must not interrupt
* provider traffic , browser delivery , or teardown .
* /
private recordAnalytics ( operation : string , write : ( ) = > void ) : void {
try {
write ( ) ;
} catch ( error ) {
let message = 'Unknown error' ;
try {
message = error instanceof Error ? error.message : String ( error ) ;
} catch {
// Even a hostile thrown value with a broken toString stays contained.
}
try {
this . logger . error ( 'analytics' , ` Failed to record ${ operation } . ` , { message } ) ;
} catch {
// Telemetry failure reporting is best-effort too.
}
}
}
/ * *
/ * *
* Verifies a ` debug.subscribe ` request and replies with the result . In
* Verifies a ` debug.subscribe ` request and replies with the result . In
* dev , any request succeeds ( frictionless — see docs / LOGGING . md ) ; in
* dev , any request succeeds ( frictionless — see docs / LOGGING . md ) ; in