Skip to content

Commit b0dbbf5

Browse files
Remote Session Events (#1073)
Co-authored-by: devin-ai-integration[bot] <158243242+devin-ai-integration[bot]@users.noreply.github.com>
1 parent ff277d5 commit b0dbbf5

7 files changed

Lines changed: 1695 additions & 70 deletions

File tree

.changeset/flat-pets-walk.md

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,5 @@
1+
---
2+
"@livekit/agents": patch
3+
---
4+
5+
Add remote session event handler

agents/src/constants.ts

Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -7,3 +7,16 @@ export const TOPIC_TRANSCRIPTION = 'lk.transcription';
77
export const ATTRIBUTE_TRANSCRIPTION_SEGMENT_ID = 'lk.segment_id';
88
export const ATTRIBUTE_PUBLISH_ON_BEHALF = 'lk.publish_on_behalf';
99
export const TOPIC_CHAT = 'lk.chat';
10+
11+
export const ATTRIBUTE_AGENT_STATE = 'lk.agent.state';
12+
export const ATTRIBUTE_AGENT_NAME = 'lk.agent.name';
13+
14+
// TODO(eval): export const ATTRIBUTE_SIMULATOR = 'lk.simulator';
15+
16+
export const TOPIC_CLIENT_EVENTS = 'lk.agent.events';
17+
export const RPC_GET_SESSION_STATE = 'lk.agent.get_session_state';
18+
export const RPC_GET_CHAT_HISTORY = 'lk.agent.get_chat_history';
19+
export const RPC_GET_AGENT_INFO = 'lk.agent.get_agent_info';
20+
export const RPC_SEND_MESSAGE = 'lk.agent.send_message';
21+
export const TOPIC_AGENT_REQUEST = 'lk.agent.request';
22+
export const TOPIC_AGENT_RESPONSE = 'lk.agent.response';

agents/src/voice/agent_session.ts

Lines changed: 35 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -37,6 +37,7 @@ import type { VAD } from '../vad.js';
3737
import type { Agent } from './agent.js';
3838
import { AgentActivity } from './agent_activity.js';
3939
import type { _TurnDetector } from './audio_recognition.js';
40+
import { ClientEventsHandler } from './client_events.js';
4041
import {
4142
type AgentEvent,
4243
AgentSessionEventTypes,
@@ -60,7 +61,12 @@ import {
6061
} from './events.js';
6162
import { AgentInput, AgentOutput } from './io.js';
6263
import { 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';
6470
import type { UnknownUserData } from './run_context.js';
6571
import type { SpeechHandle } from './speech_handle.js';
6672
import { 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

Comments
 (0)