-
Notifications
You must be signed in to change notification settings - Fork 265
feat: migrate to Agents SDK (useSession hook) #298
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
base: main
Are you sure you want to change the base?
feat: migrate to Agents SDK (useSession hook) #298
Conversation
|
The latest updates on your projects. Learn more about Vercel for GitHub.
|
5087f3f to
71fac98
Compare
71fac98 to
2b9eeb9
Compare
2b9eeb9 to
7f77240
Compare
b595bdf to
b6ab0a3
Compare
b6ab0a3 to
abca768
Compare
abca768 to
37e53d8
Compare
37e53d8 to
4d1da91
Compare
4d1da91 to
33eba9e
Compare
33eba9e to
2a528dc
Compare
2a528dc to
554bbba
Compare
hooks/useConnection.tsx
Outdated
| const session = useSession(tokenSource, sessionOptions); | ||
|
|
||
| const value = useMemo( | ||
| () => ({ | ||
| isSessionActive, | ||
| startSession: (startSession = true) => { | ||
| setIsSessionActive(true); | ||
| if (startSession) { | ||
| session.start(); | ||
| } | ||
| }, | ||
| endSession: (endSession = true) => { | ||
| setIsSessionActive(false); | ||
| if (endSession) { | ||
| session.end(); | ||
| } | ||
| }, | ||
| }), | ||
| // session object is not a stable reference | ||
| // TODO: add session object to dependencies | ||
| /* eslint-disable-next-line react-hooks/exhaustive-deps */ | ||
| [isSessionActive] | ||
| ); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Would love to get your thoughts on this,
I want control over the enter and exit transitions, delaying the ending of the session until the transition has complete, to prevent the video avatar disappearing before the exit transition has completed.
Also,
I'm noticing session isn't a stable reference (even if I pass in stable references) so to prevent it triggering context re-renders I've left it out of the dependency array
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I want control over the enter and exit transitions, delaying the ending of the session until the transition has complete, to prevent the video avatar disappearing before the exit transition has completed.
I'm not sure that I understand where this is being used - can you point me to the call sites where startSession / endSession is being called with that boolean parameter? I did see this which looks like it might be related to the problem you are mentioning but it doesn't seem to be using this app session abstraction.
I'm noticing session isn't a stable reference (even if I pass in stable references) so to prevent it triggering context re-renders I've left it out of the dependency array
Huh, that is definitely not intentional - it should be. I'll look into this, thanks for surfacing it!
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
@thomasyuill-livekit FYI that I think I figured out the useSession return stability issue. I've got a pull request fixing it here: livekit/components-js#1230
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
can you point me to the call sites where startSession / endSession
@1egoman
it's being handled here
https://github.com/livekit-examples/agent-starter-react/pull/298/files#diff-b4576c1322a558b6186c64fd05018928d9815d04717dff4ecc33d36a96d56aefR38-R48
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
That link you posted above links me to a section of the code where I see startSession is being defined (const { isSessionActive, startSession } = useAppSession();) but not where it is used.
Scanning through the diff, I see it is used here, where it is being passed as onStartCall into MotionWelcomeView, but onStartCall isn't being called with parameters (from here) so I'm not sure how this boolean parameter is being used to conditionally call session.start() / session.end().
Am I missing something? Sorry if so, just want to make sure I'm properly able to map this out in my head.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I added some additional comments to the useAppSession context value to document this behaviour
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Ah I see! Thank you for that additional context.
I will rephrase my understanding of the problem, just to make sure I have it right - clicking disconnect should update the application's state so that an animation can occur, and once the animation completes then you want to call session.end() to actually end the session. This is important so that the livekit generated video streams that are active stay active throughout the duration of the animation.
Assuming I have that right - my initial take is this is going to be inevitably fairly nuanced given there's a series of steps that the system must progress through asynchronously. It's possible that the agent sdk could do more to help with this, and I'll throw a few ideas below but I'm not sure if the agent sdk should really have to care about this given it's a problem fairly outside of its responsibility. I could be convinced though, because I could imagine this would be a fairly common rough edge.
A few potential options that come to mind in the collapsed section below:
1. Continue to push handling this down to downstream client applications
IMO if this is what is decided, I think the thing that makes what you currently have fairly confusing to me at least is that startSession / endSession currently do multiple things. I think breaking this up into two methods, making it clear one should be called "when animation starts" and one called "when animation ends" would help a lot.
2. Some sort of "keep the session running while this is rendered" component
Update the agents sdk to add some sort of component that can be rendered to keep the session "stuck" open while the animation is running. Then when react-motion or a similar tool finished the animation and unmounts the dom, the component cleans up some effect, which causes the session to really end.
Rough example:
const session = useSession(/* ... */);
// Somewhere one or multiple of these could be rendered (ie, alongside the avatar):
<KeepSessionConnected session={session} />
// Then later on:
const promise = session.end();
// session.status is now "disconnecting"
// ( ... animation occurs ... )
// session.status is STILL "disconnecting"
// Finally, the `KeepSessionConnected` component is unmounted
// session.status is STILL "disconnected"
// `promise` resolves
Important considerations:
- There probably would need to be a new
disconnecting state or something similar that can be used to make it clear that the disconnect is now async. That being said, this probably should have existed already since room.disconnect is inherently async.
- How does this interact with the
failed / other future non-disconnect terminal states? ie, would disconnecting after a session has failed also go to disconnecting or would this behavior only apply for happy path scenarios? IMO, I think it should always go through disconnecting (and maybe that is just not a great name for what this actually is).
3. Some sort of "before end" type event
Update the agents sdk to add some sort of async callback to session.end, which could return a promise that could allow you to wait for the animation to complete before continuing. So something like:
const session = useSession(/* ... */);
// Later on:
// session.state is "connected"
session.end(async () => {
// session.state is "disconnecting"
// wait for animation and resolve once complete
});
// session.state is "disconnected"
Important considerations: Same as 2
1. Continue to push handling this down to downstream client applications
IMO if this is what is decided, I think the thing that makes what you currently have fairly confusing to me at least is that startSession / endSession currently do multiple things. I think breaking this up into two methods, making it clear one should be called "when animation starts" and one called "when animation ends" would help a lot.
2. Some sort of "keep the session running while this is rendered" component
Update the agents sdk to add some sort of component that can be rendered to keep the session "stuck" open while the animation is running. Then when react-motion or a similar tool finished the animation and unmounts the dom, the component cleans up some effect, which causes the session to really end.
Rough example:
const session = useSession(/* ... */);
// Somewhere one or multiple of these could be rendered (ie, alongside the avatar):
<KeepSessionConnected session={session} />
// Then later on:
const promise = session.end();
// session.status is now "disconnecting"
// ( ... animation occurs ... )
// session.status is STILL "disconnecting"
// Finally, the `KeepSessionConnected` component is unmounted
// session.status is STILL "disconnected"
// `promise` resolvesImportant considerations:
- There probably would need to be a new
disconnectingstate or something similar that can be used to make it clear that the disconnect is now async. That being said, this probably should have existed already sinceroom.disconnectis inherently async. - How does this interact with the
failed/ other future non-disconnectterminal states? ie, would disconnecting after a session hasfailedalso go todisconnectingor would this behavior only apply for happy path scenarios? IMO, I think it should always go throughdisconnecting(and maybe that is just not a great name for what this actually is).
3. Some sort of "before end" type event
Update the agents sdk to add some sort of async callback to session.end, which could return a promise that could allow you to wait for the animation to complete before continuing. So something like:
const session = useSession(/* ... */);
// Later on:
// session.state is "connected"
session.end(async () => {
// session.state is "disconnecting"
// wait for animation and resolve once complete
});
// session.state is "disconnected"Important considerations: Same as 2
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Discussed this with @lukasIO out of band - it sounds like he's also of a similar mind that this isn't a responsibility that the agents sdk should take on.
I think (at least to me) what makes the current abstractions hard to reason about is that the animation state is so coupled with the session state. Is there anything you can do to decouple this from useSession's state?
Also, I think the name AppSession is sufficiently close to Session to also make things confusing. If you need to keep this context around, maybe there's something else you could call it to make it clear that it's not necessarily directly related to the existing useSession hook / SessionProvider component.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I agree it shouldn't be the responsibility of the SDK, this is the concern of the application and it's views.
On the surface it doesn't seem that complicated, fade out the view, then end the session, but the exectution does seem to require some indirection.
I've explored your suggestion on the client side to decouple the animation state and session state but I found the code harder to manage and reason about as there are twice as many conditions and states to check.
I've renamed useAppSession and the related artifacts to useConnection, though I do feel that introduces it's own opportunities for confusion as it's just a set of utils for managing the app's agent session and corresponding view states.
I'm currently exploring a path where you can call 'deferDisconnect` to opt out of the default behaviour and receive a callback to end the session manually, but I'm personally not finding it to be more intuitive, just a different method of indirection. I've got a few rough edges to work out so I haven't committed anything yet
This just seems to be one of the complexities that arises from the presentation layer when creating a more polished UX.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
After sleeping on it, I've scrapped the deferred disconnect approach.
I'm going with the more explicit approach you suggested.
I have 2 disconnect functions,
startDisconnectwhich starts the transitionsonDisconnectTransitionCompletethat handles ending the session
I feel this makes the dance steps explicit, and clearer to follow
554bbba to
70d9de0
Compare
7d0f187 to
5061173
Compare
| messageOrigin={messageOrigin} | ||
| hasBeenEdited={hasBeenEdited} | ||
| {...MESSAGE_MOTION_PROPS} |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
nitpick: is it worth continuing to pass through this hasBeenEdited prop when the ReceivedMessage is the subtype of ReceivedChatMessage?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
because this is an app scoped component, and we've updated the dependencies,
I don't feel we need to support the old type because it will never be true because we're now using useSessionMessages
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I don't have enough context on this to have a super strong opinion, so I'll leave it up to you.
The thing I just want to make sure that is clear though is a message of type ReceivedMessage can sometimes have a editTimestamp parameter if it falls under one of its discriminated union branches - ie, message.type === 'chatMessage' ? message.editTimestamp : null typechecks properly.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
useSessionMessages appears to returns the ReceivedMessage subtype and not ReceivedChatMessage because it handles the array composition and transform we previously did in the app
5061173 to
5020567
Compare
f85a658 to
d435989
Compare
d435989 to
7e18aae
Compare
7e18aae to
6d823c7
Compare
Migrate to LiveKit React Agent SDK
This PR integrates the LiveKit React Agent SDK (@livekit/[email protected]), replacing custom implementations with SDK-provided hooks and providers.
Key Changes
Session Management
Hooks & State
Type Updates
Component Updates
Other