Skip to content

fix: support ElevenLabs non legacy voices#1054

Open
adriablancafort wants to merge 1 commit intolivekit:mainfrom
adriablancafort:main
Open

fix: support ElevenLabs non legacy voices#1054
adriablancafort wants to merge 1 commit intolivekit:mainfrom
adriablancafort:main

Conversation

@adriablancafort
Copy link

@adriablancafort adriablancafort commented Feb 14, 2026

Description

The current LiveKit ElevenLabs plugin only supports legacy voices.
When I tried to use it with a default ElevenLabs voice, it didn't play the audio.
I realized that with this simple fix it would work with non legacy voices.

Root cause: The ElevenLabs WebSocket API can return the context id as context_id (snake_case) instead of contextId (camelCase). The plugin only read data.contextId, so when the API sent context_id the lookup failed and messages were dropped (no audio).

Changes Made

  • In src/tts.ts, read context id from both data.contextId and data.context_id when handling WebSocket messages (contextId = data.contextId ?? data.context_id).

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: A small video demo showing changes works as expected and did not break any existing functionality using Agent Playground (if applicable)

Testing

  • Automated tests added/updated (if applicable)
  • All tests pass
  • Make sure both restaurant_agent.ts and realtime_agent.ts work properly (for major changes)

Tested with a non-legacy default ElevenLabs voice; TTS audio now plays. Legacy voices still work.

@changeset-bot
Copy link

changeset-bot bot commented Feb 14, 2026

⚠️ No Changeset found

Latest commit: 364ee0d

Merging this PR will not cause a version bump for any packages. If these changes should not result in a new version, you're good to go. If these changes should result in a version bump, you need to add a changeset.

This PR includes no changesets

When changesets are added to this PR, you'll see the packages that this PR includes changesets for and the associated semver types

Click here to learn what changesets are, and how to add one.

Click here if you're a maintainer who wants to add a changeset to this PR

@CLAassistant
Copy link

CLAassistant commented Feb 14, 2026

CLA assistant check
All committers have signed the CLA.

Copy link
Contributor

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

Choose a reason for hiding this comment

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

Devin Review found 2 potential issues.

View 4 additional findings in Devin Review.

Open in Devin Review

Copy link
Contributor

Choose a reason for hiding this comment

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

🔴 isFinal not checked as is_final, causing stream to never complete for non-legacy voices

The PR fixes contextId to also check context_id (snake_case) for non-legacy ElevenLabs voices, but the same snake_case issue is not addressed for data.isFinal on line 554. If the non-legacy API returns is_final (snake_case, consistent with returning context_id), the if (data.isFinal) check will always be falsy.

Root cause and impact

When data.isFinal is never truthy:

  • stream.markDone() is never called, so #streamDone stays false
  • ctx.waiter.resolve() is never called, so the waiterPromise in Promise.all at plugins/elevenlabs/src/tts.ts:1080 never resolves
  • #cleanupContext(contextId!) is never called, leaking context data
  • The audioProcessTask loop at plugins/elevenlabs/src/tts.ts:1041-1063 spins indefinitely because #streamDone is never set to true

Audio data may still play (since data.audio is processed before the isFinal check), but the stream never properly terminates. The Promise.all hangs, leading to resource leaks and the synthesize stream never completing.

The fix should mirror the contextId fix:

if (data.isFinal ?? data.is_final) {

(Refers to line 554)

Open in Devin Review

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

Copy link
Contributor

Choose a reason for hiding this comment

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

@adriablancafort should we also handle snake_case for other fields to keep things consistent? Or is this only an issue for context_id?

Copy link
Contributor

Choose a reason for hiding this comment

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

🔴 normalizedAlignment not checked as normalized_alignment for non-legacy voices

Following the same pattern as the contextId/context_id fix, data.normalizedAlignment on line 488 may be returned as normalized_alignment by non-legacy ElevenLabs voices. When the user has preferredAlignment: 'normalized' (the default per line 695), the alignment data will be undefined and no timed word transcripts will be generated.

Root cause and impact

At plugins/elevenlabs/src/tts.ts:486-489:

const alignment =
  this.#opts.preferredAlignment === 'normalized'
    ? (data.normalizedAlignment as Record<string, unknown>)
    : (data.alignment as Record<string, unknown>);

Since preferredAlignment defaults to 'normalized' (plugins/elevenlabs/src/tts.ts:695), non-legacy voices that return normalized_alignment instead of normalizedAlignment will have alignment resolve to undefined. This means the entire alignment processing block at lines 491-546 is skipped, and no timed word transcripts are produced. While audio still plays, transcript synchronization features (word timing) will silently fail.

(Refers to lines 488-489)

Open in Devin Review

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

@toubatbrian
Copy link
Contributor

Thanks for catching this issue!

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.

3 participants