Skip to content

Commit dd07946

Browse files
committed
feat: move useAgentTimeoutIdStore away from zustand and put it at the session level
1 parent 05debba commit dd07946

File tree

3 files changed

+105
-88
lines changed

3 files changed

+105
-88
lines changed

packages/react/src/hooks/index.ts

Lines changed: 7 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -58,6 +58,12 @@ export * from './useTextStream';
5858
export * from './useTranscriptions';
5959
export * from './useSequentialRoomConnectDisconnect';
6060
export * from './useSession';
61-
export * from './useAgent';
61+
export {
62+
type AgentState,
63+
AgentEvent,
64+
type AgentCallbacks,
65+
type UseAgentReturn,
66+
useAgent,
67+
} from './useAgent';
6268
export * from './useEvents';
6369
export * from './useSessionMessages';

packages/react/src/hooks/useAgent.ts

Lines changed: 55 additions & 74 deletions
Original file line numberDiff line numberDiff line change
@@ -10,7 +10,6 @@ import {
1010
import type TypedEventEmitter from 'typed-emitter';
1111
import { EventEmitter } from 'events';
1212
import * as React from 'react';
13-
import { create } from 'zustand';
1413
import { ParticipantAgentAttributes, TrackReference } from '@livekit/components-core';
1514

1615
import { useParticipantTracks } from './useParticipantTracks';
@@ -172,91 +171,73 @@ const generateDerivedStateValues = <State extends AgentState>(state: State) =>
172171
isAvailable: State extends 'listening' | 'thinking' | 'speaking' ? true : false;
173172
};
174173

175-
const useAgentTimeoutIdStore = create<{
174+
/** Internal hook used by useSession to store global agent state */
175+
export const useAgentTimeoutIdStore = (): {
176176
agentTimeoutFailureReason: string | null;
177177
startAgentTimeout: (agentConnectTimeoutMilliseconds?: number) => void;
178178
clearAgentTimeout: () => void;
179179
updateAgentTimeoutState: (agentState: AgentState) => void;
180180
updateAgentTimeoutParticipantExists: (agentParticipantExists: boolean) => void;
181-
internal: {
182-
agentTimeoutId: ReturnType<typeof setTimeout> | null;
183-
agentState: AgentState;
184-
agentParticipantExists: boolean;
185-
};
186-
}>((set, get) => {
181+
} => {
182+
const [agentTimeoutFailureReason, setAgentTimeoutFailureReason] = React.useState<string | null>(
183+
null,
184+
);
185+
const [agentTimeoutId, setAgentTimeoutId] = React.useState<ReturnType<typeof setTimeout> | null>(
186+
null,
187+
);
188+
189+
const agentStateRef = React.useRef<AgentState>('connecting');
190+
const agentParticipantExistsRef = React.useRef(false);
191+
187192
const startAgentConnectedTimeout = (agentConnectTimeoutMilliseconds?: number) => {
188193
return setTimeout(() => {
189-
const {
190-
internal: { agentState, agentParticipantExists },
191-
} = get();
192-
if (!agentParticipantExists) {
193-
set((old) => ({ ...old, agentTimeoutFailureReason: 'Agent did not join the room.' }));
194+
if (!agentParticipantExistsRef.current) {
195+
setAgentTimeoutFailureReason('Agent did not join the room.');
194196
return;
195197
}
196198

197-
const { isAvailable } = generateDerivedStateValues(agentState);
199+
const { isAvailable } = generateDerivedStateValues(agentStateRef.current);
198200
if (!isAvailable) {
199-
set((old) => ({
200-
...old,
201-
agentTimeoutFailureReason: 'Agent connected but did not complete initializing.',
202-
}));
201+
setAgentTimeoutFailureReason('Agent connected but did not complete initializing.');
203202
return;
204203
}
205204
}, agentConnectTimeoutMilliseconds ?? DEFAULT_AGENT_CONNECT_TIMEOUT_MILLISECONDS);
206205
};
207206

208207
return {
209-
agentTimeoutFailureReason: null,
210-
startAgentTimeout: (agentConnectTimeoutMilliseconds?: number) => {
211-
set((old) => {
212-
if (old.internal.agentTimeoutId) {
213-
clearTimeout(old.internal.agentTimeoutId);
214-
}
215-
216-
return {
217-
...old,
218-
agentTimeoutFailureReason: null,
219-
internal: {
220-
...old.internal,
221-
agentTimeoutId: startAgentConnectedTimeout(agentConnectTimeoutMilliseconds),
222-
agentState: 'connecting',
223-
agentParticipantExists: false,
224-
},
225-
};
226-
});
227-
},
228-
clearAgentTimeout: () => {
229-
set((old) => {
230-
if (old.internal.agentTimeoutId) {
231-
clearTimeout(old.internal.agentTimeoutId);
208+
agentTimeoutFailureReason,
209+
startAgentTimeout: React.useCallback(
210+
(agentConnectTimeoutMilliseconds?: number) => {
211+
if (agentTimeoutId) {
212+
clearTimeout(agentTimeoutId);
232213
}
233-
return {
234-
...old,
235-
agentTimeoutFailureReason: null,
236-
internal: {
237-
...old.internal,
238-
agentTimeoutId: null,
239-
agentState: 'connecting',
240-
agentParticipantExists: false,
241-
},
242-
};
243-
});
244-
},
245214

246-
updateAgentTimeoutState: (agentState: AgentState) => {
247-
set((old) => ({ ...old, internal: { ...old.internal, agentState: agentState } }));
248-
},
249-
updateAgentTimeoutParticipantExists: (agentParticipantExists: boolean) => {
250-
set((old) => ({ ...old, internal: { ...old.internal, agentParticipantExists } }));
251-
},
215+
setAgentTimeoutFailureReason(null);
216+
setAgentTimeoutId(startAgentConnectedTimeout(agentConnectTimeoutMilliseconds));
217+
agentStateRef.current = 'connecting';
218+
agentParticipantExistsRef.current = false;
219+
},
220+
[agentTimeoutId],
221+
),
222+
clearAgentTimeout: React.useCallback(() => {
223+
if (agentTimeoutId) {
224+
clearTimeout(agentTimeoutId);
225+
}
252226

253-
internal: {
254-
agentTimeoutId: null,
255-
agentState: 'connecting',
256-
agentParticipantExists: false,
257-
},
227+
setAgentTimeoutFailureReason(null);
228+
setAgentTimeoutId(null);
229+
agentStateRef.current = 'connecting';
230+
agentParticipantExistsRef.current = false;
231+
}, [agentTimeoutId]),
232+
233+
updateAgentTimeoutState: React.useCallback((agentState: AgentState) => {
234+
agentStateRef.current = agentState;
235+
}, []),
236+
updateAgentTimeoutParticipantExists: React.useCallback((agentParticipantExists: boolean) => {
237+
agentParticipantExistsRef.current = agentParticipantExists;
238+
}, []),
258239
};
259-
});
240+
};
260241

261242
type SessionStub = Pick<UseSessionReturn, 'connectionState' | 'room' | 'internal'>;
262243

@@ -275,7 +256,15 @@ export function useAgent(session?: SessionStub): UseAgentReturn {
275256

276257
const {
277258
room,
278-
internal: { agentConnectTimeoutMilliseconds },
259+
internal: {
260+
agentConnectTimeoutMilliseconds,
261+
262+
agentTimeoutFailureReason,
263+
startAgentTimeout,
264+
clearAgentTimeout,
265+
updateAgentTimeoutState,
266+
updateAgentTimeoutParticipantExists,
267+
},
279268
} = session;
280269

281270
const emitter = React.useMemo(() => new EventEmitter() as TypedEventEmitter<AgentCallbacks>, []);
@@ -398,14 +387,6 @@ export function useAgent(session?: SessionStub): UseAgentReturn {
398387
};
399388
}, [room.localParticipant]);
400389

401-
const {
402-
agentTimeoutFailureReason,
403-
startAgentTimeout,
404-
clearAgentTimeout,
405-
updateAgentTimeoutState,
406-
updateAgentTimeoutParticipantExists,
407-
} = useAgentTimeoutIdStore();
408-
409390
const failureReasons = React.useMemo(() => {
410391
return agentTimeoutFailureReason ? [agentTimeoutFailureReason] : [];
411392
}, [agentTimeoutFailureReason]);

packages/react/src/hooks/useSession.ts

Lines changed: 43 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -14,7 +14,7 @@ import {
1414
import { EventEmitter } from 'events';
1515

1616
import { useMaybeRoomContext } from '../context';
17-
import { useAgent } from './useAgent';
17+
import { AgentState, useAgent, useAgentTimeoutIdStore } from './useAgent';
1818
import { TrackReference } from '@livekit/components-core';
1919
import { useLocalParticipant } from './useLocalParticipant';
2020

@@ -72,6 +72,12 @@ type SessionStateCommon = {
7272
emitter: TypedEventEmitter<SessionCallbacks>;
7373
tokenSource: TokenSourceConfigurable | TokenSourceFixed;
7474
agentConnectTimeoutMilliseconds?: number;
75+
76+
agentTimeoutFailureReason: string | null;
77+
startAgentTimeout: (agentConnectTimeoutMilliseconds?: number) => void;
78+
clearAgentTimeout: () => void;
79+
updateAgentTimeoutState: (agentState: AgentState) => void;
80+
updateAgentTimeoutParticipantExists: (agentParticipantExists: boolean) => void;
7581
};
7682
};
7783

@@ -264,17 +270,45 @@ export function useSession(
264270
};
265271
}, [localParticipant, microphonePublication, microphonePublication?.isMuted]);
266272

273+
const {
274+
agentTimeoutFailureReason,
275+
startAgentTimeout,
276+
clearAgentTimeout,
277+
updateAgentTimeoutState,
278+
updateAgentTimeoutParticipantExists,
279+
} = useAgentTimeoutIdStore();
280+
281+
const sessionInternal: UseSessionReturn['internal'] = React.useMemo(
282+
() => ({
283+
emitter,
284+
tokenSource,
285+
agentConnectTimeoutMilliseconds,
286+
287+
agentTimeoutFailureReason,
288+
startAgentTimeout,
289+
clearAgentTimeout,
290+
updateAgentTimeoutState,
291+
updateAgentTimeoutParticipantExists,
292+
}),
293+
[
294+
emitter,
295+
agentConnectTimeoutMilliseconds,
296+
tokenSource,
297+
agentTimeoutFailureReason,
298+
startAgentTimeout,
299+
clearAgentTimeout,
300+
updateAgentTimeoutState,
301+
updateAgentTimeoutParticipantExists,
302+
],
303+
);
304+
267305
const conversationState = React.useMemo(():
268306
| SessionStateConnecting
269307
| SessionStateConnected
270308
| SessionStateDisconnected => {
271309
const common: SessionStateCommon = {
272310
room,
273-
internal: {
274-
emitter,
275-
tokenSource,
276-
agentConnectTimeoutMilliseconds,
277-
},
311+
internal: sessionInternal,
278312
};
279313

280314
switch (roomConnectionState) {
@@ -320,8 +354,7 @@ export function useSession(
320354
};
321355
}
322356
}, [
323-
tokenSource,
324-
agentConnectTimeoutMilliseconds,
357+
sessionInternal,
325358
room,
326359
emitter,
327360
roomConnectionState,
@@ -387,12 +420,9 @@ export function useSession(
387420
() => ({
388421
connectionState: conversationState.connectionState,
389422
room,
390-
internal: {
391-
emitter,
392-
tokenSource,
393-
},
423+
internal: sessionInternal,
394424
}),
395-
[conversationState, emitter, room, tokenSource],
425+
[conversationState, room, sessionInternal],
396426
),
397427
);
398428

0 commit comments

Comments
 (0)