Skip to content

feat(voice): auto-disable realtime server-side turn detection - #2158

Open
rosetta-livekit-bot[bot] wants to merge 2 commits into
mainfrom
enshrine-avionics-denudes
Open

feat(voice): auto-disable realtime server-side turn detection#2158
rosetta-livekit-bot[bot] wants to merge 2 commits into
mainfrom
enshrine-avionics-denudes

Conversation

@rosetta-livekit-bot

@rosetta-livekit-bot rosetta-livekit-bot Bot commented Jul 29, 2026

Copy link
Copy Markdown
Contributor

Ports livekit/agents PR #6495.

Summary

  • Add realtime canDisableTurnDetection capability and per-session turnDetectionDisabled creation option.
  • Resolve realtime server-side turn detection once per voice activity and auto-disable it when client-side turn-taking is explicitly configured.
  • Preserve OpenAI/Azure default turn-detection disable capability, keep xAI unsupported, and adapt Google/Phonic signatures.
  • Add JS coverage for realtime turn-detection resolution, runtime warnings, session creation, handoff reuse, and OpenAI realtime defaults.
Source diff coverage
Source file Classification
livekit-agents/livekit/agents/llm/realtime.py ported to agents/src/llm/realtime.ts: added canDisableTurnDetection capability and session({ turnDetectionDisabled }) option.
livekit-agents/livekit/agents/llm/realtime_fallback_adapter.py not applicable: target has no realtime fallback adapter counterpart.
livekit-agents/livekit/agents/voice/agent_activity.py adapted to agents/src/voice/agent_activity.ts: ported session-scoped realtime turn-detection resolution, validation/warnings, session creation flag, reuse guard, VAD wiring, interruption checks, commit behavior, and gatekeeping checks using TS naming and existing target flow.
livekit-agents/livekit/agents/voice/agent_session.py adapted to agents/src/voice/agent_session.ts: track whether turnDetection was explicitly supplied and update that state on runtime changes.
livekit-plugins/livekit-plugins-aws/livekit/plugins/aws/experimental/realtime/realtime_model.py not applicable: target repo has no AWS realtime plugin counterpart.
livekit-plugins/livekit-plugins-google/livekit/plugins/google/realtime/realtime_api.py adapted to plugins/google/src/realtime/realtime_api.ts: accept the new session option while leaving disabling unsupported.
livekit-plugins/livekit-plugins-nvidia/livekit/plugins/nvidia/experimental/realtime/realtime_model.py not applicable: target repo has no NVIDIA realtime plugin counterpart.
livekit-plugins/livekit-plugins-openai/livekit/plugins/openai/realtime/realtime_model.py ported to plugins/openai/src/realtime/realtime_model.ts: mark default server VAD as disable-capable, preserve Azure intent, and apply per-session disabled turn detection. Python's model-level update_options propagation test has no JS counterpart because this target model has no model-level realtime updateOptions API.
livekit-plugins/livekit-plugins-phonic/livekit/plugins/phonic/realtime/realtime_model.py adapted to plugins/phonic/src/realtime/realtime_model.ts: accept the new session option while leaving disabling unsupported.
livekit-plugins/livekit-plugins-ultravox/livekit/plugins/ultravox/realtime/realtime_model.py not applicable: target repo has no Ultravox realtime plugin counterpart.
livekit-plugins/livekit-plugins-xai/livekit/plugins/xai/realtime/realtime_model.py adapted to plugins/xai/src/realtime/realtime_model.ts: keep OpenAI-derived disabling unsupported for xAI and ignore the new session option.
tests/fake_realtime.py adapted in agents/src/voice/realtime_auto_disable_turn_detection.test.ts: JS fake realtime model/session records turnDetectionDisabled for ported tests.
tests/test_realtime/test_openai_realtime_model.py ported to plugins/openai/src/realtime/realtime_model.test.ts: added OpenAI realtime default/disabled/Azure/session assertions.
tests/test_realtime_auto_disable_turn_detection.py ported/adapted to agents/src/voice/realtime_auto_disable_turn_detection.test.ts: added equivalent JS coverage for resolution matrix, runtime warnings, default warning suppression, session creation flag, and handoff reuse behavior. Fallback adapter cases are not applicable because the target lacks that adapter.

Verification

  • pnpm build
  • OPENAI_API_KEY= pnpm test agents plugins/openai plugins/google plugins/phonic plugins/xai
  • pnpm lint
  • cue-cli runtime validation with a temporary fake realtime JS agent: asserted conversation_item_added(.item.message.content[0].text =~ "rtTurnDetectionEnabled.*false.*turnDetectionDisabled.*true")

Notes

  • Running the same package test command with the ambient OPENAI_API_KEY enabled optional OpenAI integration tests; those failed on local Silero/model setup unrelated to this port. The rerun above unsets OPENAI_API_KEY so optional external tests are skipped.
  • Root pnpm api:check is blocked by an unrelated package (@livekit/agents-plugin-liveavatar) without api-extractor.json; filtered API checks for touched packages are currently blocked in @livekit/agents by API Extractor's existing export * as limitation against agents/dist/index.d.ts.

Ported from livekit/agents#6495

Original PR description

Auto-disables a realtime model's server-side turn detection for the session when the user configured client-side turn-taking (a TurnDetector, vad, manual, or an explicit interruption mode) and the model supports it, so client-side EoT/barge-in works without also passing turn_detection=None to the RealtimeModel.

Adds RealtimeCapabilities.can_disable_turn_detection (OpenAI sets it from the ctor turn_detection given-ness) and a session(turn_detection_disabled=...) seam applied on the initial session.update via the per-session opts copy, so the model is never mutated and stays reusable. AgentActivity resolves the effective state once (_rt_turn_detection_enabled) and routes every server-side turn-detection check through it.

Other realtime plugins accept-and-ignore the flag (can_disable_turn_detection stays False): xAI (client turn-taking not stable in testing) and Gemini/others (no commit_audio/clear_audio; turns are activity-signal based).

Stacked on longc/realtime-barge-in; this PR targets that branch, not main.

@rosetta-livekit-bot
rosetta-livekit-bot Bot requested a review from a team as a code owner July 29, 2026 09:31
@rosetta-livekit-bot
rosetta-livekit-bot Bot requested a review from longcw July 29, 2026 09:32

@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: No Issues Found

Devin Review analyzed this PR and found no potential bugs to report.

View in Devin Review to see 1 additional finding.

Open in Devin Review

- restore withAzure's destructured signature; the pre-existing code already
  reassigned its params, so only the default needed moving out
- name the session options type instead of repeating the object literal
- drop xAI's session() override, a no-op the canDisableTurnDetection gate
  already covers
- use a template literal for the runtime-change warning
@changeset-bot

changeset-bot Bot commented Jul 30, 2026

Copy link
Copy Markdown

🦋 Changeset detected

Latest commit: 6d36985

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 Major
@livekit/agents-plugin-openai Major
@livekit/agents-plugin-google Major
@livekit/agents-plugin-phonic Major
@livekit/agents-plugin-xai Major
@livekit/agents-plugin-anam Major
@livekit/agents-plugin-anthropic Major
@livekit/agents-plugin-assemblyai Major
@livekit/agents-plugin-azure Major
@livekit/agents-plugin-baseten Major
@livekit/agents-plugin-bey Major
@livekit/agents-plugin-cartesia Major
@livekit/agents-plugin-cerebras Major
@livekit/agents-plugin-deepgram Major
@livekit/agents-plugin-did Major
@livekit/agents-plugin-elevenlabs Major
@livekit/agents-plugin-fishaudio Major
@livekit/agents-plugin-hedra Major
@livekit/agents-plugin-hume Major
@livekit/agents-plugin-inworld Major
@livekit/agents-plugin-krisp Major
@livekit/agents-plugin-lemonslice Major
@livekit/agents-plugin-liveavatar Major
@livekit/agents-plugin-livekit Major
@livekit/agents-plugin-minimax Major
@livekit/agents-plugin-mistral Major
@livekit/agents-plugin-mistralai Major
@livekit/agents-plugin-neuphonic Major
@livekit/agents-plugin-perplexity Major
@livekit/agents-plugin-protoface Major
@livekit/agents-plugin-resemble Major
@livekit/agents-plugin-rime Major
@livekit/agents-plugin-runway Major
@livekit/agents-plugin-sarvam Major
@livekit/agents-plugin-silero Major
@livekit/agents-plugin-soniox Major
@livekit/agents-plugin-tavus Major
@livekit/agents-plugins-test Major
@livekit/agents-plugin-trugen Major

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

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

View 2 additional findings in Devin Review.

Open in Devin Review

Comment on lines 1122 to 1124
if (hasTurnDetection) {
recognitionOptions.turnDetection = turnDetection;
recognitionOptions.turnDetection = this._resolvedTurnDetection;
}

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.

🔴 Turning off a running session's turn-detection setting no longer takes effect in the speech recognizer

When turn detection is cleared at runtime, the recognizer is handed an "unchanged" value instead of an explicit reset (recognitionOptions.turnDetection = this._resolvedTurnDetection at agents/src/voice/agent_activity.ts:1123), so the previously configured mode (e.g. manual) keeps driving speech recognition.
Impact: After switching turn detection back to automatic, the agent can keep behaving as if the old mode (such as manual push-to-talk) were still active and stop replying on its own.

Null vs undefined semantics in AudioRecognition.updateOptions

AudioRecognition.updateOptions treats undefined as "leave unchanged" and only resets when a value (including null) is supplied: if (options.turnDetection !== undefined) { this.turnDetectionMode = options.turnDetection ?? undefined; } (agents/src/voice/audio_recognition.ts:543-545).

Previously the raw parameter was forwarded, so AgentSession.updateOptions({ turnDetection: null }) propagated null and cleared the recognizer's mode. Now _resolvedTurnDetection is forwarded, and _resolveTurnDetection(null ?? undefined) returns undefined, so the reset is silently dropped while this.turnDetectionMode on the activity is cleared — the two diverge. turnDetectionMode === 'manual' gates auto-commit of user turns in the recognizer (agents/src/voice/audio_recognition.ts:1204, 1377, 1396).

Suggested change
if (hasTurnDetection) {
recognitionOptions.turnDetection = turnDetection;
recognitionOptions.turnDetection = this._resolvedTurnDetection;
}
if (hasTurnDetection) {
// pass null (not undefined) so AudioRecognition clears its mode instead of keeping it
recognitionOptions.turnDetection = this._resolvedTurnDetection ?? null;
}
Open in Devin Review

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

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.

1 participant