@@ -37,6 +37,7 @@ import type { VAD } from '../vad.js';
3737import type { Agent } from './agent.js' ;
3838import { AgentActivity } from './agent_activity.js' ;
3939import type { _TurnDetector } from './audio_recognition.js' ;
40+ import { ClientEventsHandler } from './client_events.js' ;
4041import {
4142 type AgentEvent ,
4243 AgentSessionEventTypes ,
@@ -60,7 +61,12 @@ import {
6061} from './events.js' ;
6162import { AgentInput , AgentOutput } from './io.js' ;
6263import { RecorderIO } from './recorder_io/index.js' ;
63- import { RoomIO , type RoomInputOptions , type RoomOutputOptions } from './room_io/index.js' ;
64+ import {
65+ DEFAULT_TEXT_INPUT_CALLBACK ,
66+ RoomIO ,
67+ type RoomInputOptions ,
68+ type RoomOutputOptions ,
69+ } from './room_io/index.js' ;
6470import type { UnknownUserData } from './run_context.js' ;
6571import type { SpeechHandle } from './speech_handle.js' ;
6672import { RunResult } from './testing/run_result.js' ;
@@ -175,10 +181,10 @@ export class AgentSession<
175181 private activity ?: AgentActivity ;
176182 private nextActivity ?: AgentActivity ;
177183 private started = false ;
178- private userState : UserState = 'listening' ;
184+ private _userState : UserState = 'listening' ;
179185
180186 private roomIO ?: RoomIO ;
181- private logger = log ( ) ;
187+ private clientEventsHandler ?: ClientEventsHandler ;
182188
183189 private _chatCtx : ChatContext ;
184190 private _userData : UserData | undefined ;
@@ -225,6 +231,8 @@ export class AgentSession<
225231 /** @internal */
226232 _userSpeakingSpan ?: Span ;
227233
234+ private logger = log ( ) ;
235+
228236 constructor ( options : AgentSessionOptions < UserData > ) {
229237 super ( ) ;
230238
@@ -382,6 +390,13 @@ export class AgentSession<
382390 outputOptions,
383391 } ) ;
384392 this . roomIO . start ( ) ;
393+
394+ this . clientEventsHandler = new ClientEventsHandler ( this , this . roomIO ) ;
395+ if ( inputOptions ?. textEnabled !== false ) {
396+ this . clientEventsHandler . registerTextInput (
397+ inputOptions ?. textInputCallback ?? DEFAULT_TEXT_INPUT_CALLBACK ,
398+ ) ;
399+ }
385400 }
386401
387402 let ctx : JobContext | undefined = undefined ;
@@ -423,6 +438,10 @@ export class AgentSession<
423438
424439 await Promise . allSettled ( tasks ) ;
425440
441+ if ( this . clientEventsHandler ) {
442+ await this . clientEventsHandler . start ( ) ;
443+ }
444+
426445 // Log used IO configuration
427446 this . logger . debug (
428447 `using audio io: ${ this . input . audio ? '`' + this . input . audio . constructor . name + '`' : '(none)' } -> \`AgentSession\` -> ${ this . output . audio ? '`' + this . output . audio . constructor . name + '`' : '(none)' } ` ,
@@ -669,6 +688,10 @@ export class AgentSession<
669688 return this . _agentState ;
670689 }
671690
691+ get userState ( ) : UserState {
692+ return this . _userState ;
693+ }
694+
672695 get currentAgent ( ) : Agent {
673696 if ( ! this . agent ) {
674697 throw new Error ( 'AgentSession is not running' ) ;
@@ -776,7 +799,7 @@ export class AgentSession<
776799 this . _agentState = state ;
777800
778801 // Handle user away timer based on state changes
779- if ( state === 'listening' && this . userState === 'listening' ) {
802+ if ( state === 'listening' && this . _userState === 'listening' ) {
780803 this . _setUserAwayTimer ( ) ;
781804 } else {
782805 this . _cancelUserAwayTimer ( ) ;
@@ -790,7 +813,7 @@ export class AgentSession<
790813
791814 /** @internal */
792815 _updateUserState ( state : UserState , lastSpeakingTime ?: number ) {
793- if ( this . userState === state ) {
816+ if ( this . _userState === state ) {
794817 return ;
795818 }
796819
@@ -808,8 +831,8 @@ export class AgentSession<
808831 this . _userSpeakingSpan = undefined ;
809832 }
810833
811- const oldState = this . userState ;
812- this . userState = state ;
834+ const oldState = this . _userState ;
835+ this . _userState = state ;
813836
814837 // Handle user away timer based on state changes
815838 if ( state === 'listening' && this . _agentState === 'listening' ) {
@@ -864,7 +887,7 @@ export class AgentSession<
864887 }
865888
866889 private _onUserInputTranscribed ( ev : UserInputTranscribedEvent ) : void {
867- if ( this . userState === 'away' && ev . isFinal ) {
890+ if ( this . _userState === 'away' && ev . isFinal ) {
868891 this . logger . debug ( 'User returned from away state due to speech input' ) ;
869892 this . _updateUserState ( 'listening' ) ;
870893 }
@@ -925,6 +948,9 @@ export class AgentSession<
925948 this . output . audio = null ;
926949 this . output . transcription = null ;
927950
951+ await this . clientEventsHandler ?. close ( ) ;
952+ this . clientEventsHandler = undefined ;
953+
928954 await this . roomIO ?. close ( ) ;
929955 this . roomIO = undefined ;
930956
@@ -950,7 +976,7 @@ export class AgentSession<
950976
951977 this . emit ( AgentSessionEventTypes . Close , createCloseEvent ( reason , error ) ) ;
952978
953- this . userState = 'listening' ;
979+ this . _userState = 'listening' ;
954980 this . _agentState = 'initializing' ;
955981 this . rootSpanContext = undefined ;
956982 this . llmErrorCounts = 0 ;
0 commit comments