Conversation
🦋 Changeset detectedLatest commit: 60a1795 The changes in this PR will be included in the next version bump. This PR includes changesets to release 39 packages
Not sure what this means? Click here to learn what changesets are. Click here if you're a maintainer who wants to add another changeset to this PR |
Gemini replies to activityEnd on its own. The agent calls commitAudio before onUserTurnCompleted, so sending activityEnd there made it reply twice.
There was a problem hiding this comment.
Devin Review found 1 new potential issue.
2 flags not posted on this PR by your GitHub settings — view them in Devin Review. (Configure)
| * `onUserTurnCompleted`, so ending the activity here would reply twice. `generateReply()` | ||
| * ends it instead; use `endUserActivity()` to close a turn yourself. | ||
| */ | ||
| async commitAudio() {} |
There was a problem hiding this comment.
🟡 Suppressed user turns remain open
When onUserTurnCompleted stops a manual turn, commitAudio() leaves its activity open. No generateReply() follows, so the next startUserActivity() cannot open a separate turn. Gemini receives the next utterance as part of the stopped turn.
Learn more
A manual Gemini activity starts when startUserActivity sends activityStart. In the agent flow, userTurnCompletedImpl calls commitAudio() before the onUserTurnCompleted hook. If the hook throws StopResponse, userTurnCompletedImpl returns before scheduling a reply. Because commitAudio() does nothing and generateReply() is never called, inUserActivity stays true. On the next interruption, startUserActivity() sends no new start, and Gemini has not received the prior turn's activityEnd.
Example: A caller's onUserTurnCompleted rejects utterance A with StopResponse. Utterance B then begins, but its startUserActivity() sees A's activity still open; B's audio enters A's unclosed turn instead of a new turn.
Recommended fix: Provide a way to close or discard an open manual activity on the stopped-reply path, without triggering an unwanted Gemini reply. Ensure that the agent's turn-completion flow uses that path before another user activity can start.
Was this helpful? React with 👍 or 👎 to provide feedback.
There was a problem hiding this comment.
Same on main, commitAudio() was a no-op before this PR too. And Gemini replies to any activityEnd, so the activity can't be closed without a reply. Leaving this out of the PR.
Description
Fixes #2581
With manual activity detection there was no way to end a user turn opened with
startUserActivity()other thangenerateReply(). My first attempt sentactivityEndfromcommitAudio(), but Gemini replies toactivityEndon its own (checked on gemini-2.5-flash-native-audio and gemini-3.1-flash-live), and the agent callscommitAudio()beforeonUserTurnCompleted, so it replied twice.So
commitAudio()stays a no-op, now with a comment why, and there's a publicendUserActivity()next tostartUserActivity().generateReply()uses it too, so the agent flow doesn't change. If you call it yourself, Gemini answers the turn, no placeholder"."needed.Changes Made
endUserActivity(), used bygenerateReply()commitAudio()andclearAudio()Pre-Review Checklist
Testing
3 unit tests on a mocked session. vitest, prettier, eslint and tsc pass for the google plugin. Also ran a small script against the Live API with manual detection:
activityEndalone gets exactly one reply on both models. Script and output: https://gist.github.com/ragen1337/0e3c908f09c758273383e409e2cfca5cAdditional Notes
Separate thing I noticed on gemini-3.1-flash-live: the current
generateReply()path (activityEndand then a bareturnComplete) gets two replies, the first one to the empty turn. It's in the gist output too. Not touching it here, can open an issue.