Skip to content

feat(google): add endUserActivity for manual activity detection - #2636

Open
ragen1337 wants to merge 2 commits into
livekit:mainfrom
ragen1337:fix/2581-google-commit-audio
Open

ragen1337 wants to merge 2 commits into
livekit:mainfrom
ragen1337:fix/2581-google-commit-audio

Conversation

@ragen1337

@ragen1337 ragen1337 commented Oct 4, 2026 •

Copy link
Copy Markdown

Description

Fixes #2581

With manual activity detection there was no way to end a user turn opened with startUserActivity() other than generateReply(). My first attempt sent activityEnd from commitAudio(), but Gemini replies to activityEnd on its own (checked on gemini-2.5-flash-native-audio and gemini-3.1-flash-live), and the agent calls commitAudio() before onUserTurnCompleted, so it replied twice.

So commitAudio() stays a no-op, now with a comment why, and there's a public endUserActivity() next to startUserActivity(). 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

  • public endUserActivity(), used by generateReply()
  • doc comments on commitAudio() and clearAudio()
  • tests and a changeset

Pre-Review Checklist

  • Build passes: All builds (lint, typecheck, tests) pass locally
  • AI-generated code reviewed: Removed unnecessary comments and ensured code quality
  • Changes explained: All changes are properly documented and justified above
  • Scope appropriate: All changes relate to the PR title, or explanations provided for why they're included
  • Video demo: n/a, no change to the agent flow

Testing

  • Automated tests added/updated (if applicable)
  • All tests pass

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: activityEnd alone gets exactly one reply on both models. Script and output: https://gist.github.com/ragen1337/0e3c908f09c758273383e409e2cfca5c

Additional Notes

Separate thing I noticed on gemini-3.1-flash-live: the current generateReply() path (activityEnd and then a bare turnComplete) 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.

@ragen1337
ragen1337 requested a review from a team as a code owner October 4, 2026 08:27
@changeset-bot

changeset-bot Bot commented Oct 4, 2026 •

Copy link
Copy Markdown

🦋 Changeset detected

Latest commit: 60a1795

The changes in this PR will be included in the next version bump.

This PR includes changesets to release 39 packages
Name Type
@livekit/agents-plugin-google Patch
@livekit/agents Patch
@livekit/agents-plugin-anam Patch
@livekit/agents-plugin-anthropic Patch
@livekit/agents-plugin-assemblyai Patch
@livekit/agents-plugin-azure Patch
@livekit/agents-plugin-baseten Patch
@livekit/agents-plugin-bey Patch
@livekit/agents-plugin-cartesia Patch
@livekit/agents-plugin-cerebras Patch
@livekit/agents-plugin-deepgram Patch
@livekit/agents-plugin-did Patch
@livekit/agents-plugin-elevenlabs Patch
@livekit/agents-plugin-fishaudio Patch
@livekit/agents-plugin-hume Patch
@livekit/agents-plugin-inworld Patch
@livekit/agents-plugin-krisp Patch
@livekit/agents-plugin-lemonslice Patch
@livekit/agents-plugin-liveavatar Patch
@livekit/agents-plugin-livekit Patch
@livekit/agents-plugin-meta Patch
@livekit/agents-plugin-minimax Patch
@livekit/agents-plugin-mistral Patch
@livekit/agents-plugin-mistralai Patch
@livekit/agents-plugin-neuphonic Patch
@livekit/agents-plugin-openai Patch
@livekit/agents-plugin-perplexity Patch
@livekit/agents-plugin-phonic Patch
@livekit/agents-plugin-protoface Patch
@livekit/agents-plugin-resemble Patch
@livekit/agents-plugin-rime Patch
@livekit/agents-plugin-runway Patch
@livekit/agents-plugin-sarvam Patch
@livekit/agents-plugin-silero Patch
@livekit/agents-plugin-soniox Patch
@livekit/agents-plugin-tavus Patch
@livekit/agents-plugins-test Patch
@livekit/agents-plugin-trugen Patch
@livekit/agents-plugin-xai Patch

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

@CLAassistant

CLAassistant commented Oct 4, 2026 •

Copy link
Copy Markdown

CLA assistant check
All committers have signed the CLA.

devin-ai-integration[bot]

This comment was marked as resolved.

Gemini replies to activityEnd on its own. The agent calls commitAudio
before onUserTurnCompleted, so sending activityEnd there made it reply
twice.
@ragen1337 ragen1337 changed the title fix(google): send activityEnd from commitAudio with manual activity detection feat(google): add endUserActivity for manual activity detection Oct 4, 2026

@devin-ai-integration devin-ai-integration Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Devin Review found 1 new potential issue.

2 flags not posted on this PR by your GitHub settings — view them in Devin Review. (Configure)

Devin Review

* `onUserTurnCompleted`, so ending the activity here would reply twice. `generateReply()`
* ends it instead; use `endUserActivity()` to close a turn yourself.
*/
async commitAudio() {}

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🟡 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.

Devin Review


Was this helpful? React with 👍 or 👎 to provide feedback.

Copy link
Copy Markdown
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

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.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

google(realtime): commitAudio()/clearAudio() are no-ops, so manual activity detection cannot close a user turn

2 participants