Skip to content

feat(elevenlabs): stream eleven_v3/eleven_v3_conversational via text-to-dialogue - #2347

Open
rosetta-livekit-bot[bot] wants to merge 1 commit into
mainfrom
port/elevenlabs-v3-dialogue
Open

feat(elevenlabs): stream eleven_v3/eleven_v3_conversational via text-to-dialogue#2347
rosetta-livekit-bot[bot] wants to merge 1 commit into
mainfrom
port/elevenlabs-v3-dialogue

Conversation

@rosetta-livekit-bot

@rosetta-livekit-bot rosetta-livekit-bot Bot commented Aug 26, 2026

Copy link
Copy Markdown
Contributor

Ports livekit/agents#6838 to the ElevenLabs JS plugin.

Summary

  • route eleven_v3 and eleven_v3_conversational HTTP and WebSocket synthesis through ElevenLabs text-to-dialogue
  • adapt dialogue framing, option filtering, alignment, context cleanup, immediate close, and per-context keep-alives to the JS connection architecture
  • add source-equivalent HTTP/WebSocket coverage and a minor ElevenLabs changeset

Implementation gap

The JS streaming input queue does not retain replayable text after a context-level failure. Dialogue provider errors and context timeouts therefore terminate the input/tokenizer tasks and are non-retryable, rather than honoring maxRetry as the Python stream wrapper can. Porting retry parity would require a core replayable streaming-input redesign outside the ElevenLabs plugin; this PR avoids duplicate or lost text instead of silently attempting an unsafe retry.

Source diff coverage
  • livekit-plugins/livekit-plugins-elevenlabs/livekit/plugins/elevenlabs/models.py: adapted to plugins/elevenlabs/src/models.ts; adds eleven_v3_conversational and the dialogue-model prefix classifier using the TypeScript model union.
  • livekit-plugins/livekit-plugins-elevenlabs/livekit/plugins/elevenlabs/tts.py: adapted to plugins/elevenlabs/src/tts.ts; preserves text-to-dialogue HTTP and multiplexed WebSocket routing, request framing, supported-setting filtering and warnings, pronunciation dictionaries, snake-case response parsing, error handling, late-audio suppression, immediate context close, and per-context keep-alives within the target's private-field connection architecture.
  • tests/test_plugin_elevenlabs_tts.py: adapted to plugins/elevenlabs/src/tts.test.ts; ports the source model classification, endpoint/body, warning, WebSocket framing, alignment/finality, error, late-audio, close, and keep-alive scenarios with Vitest and local HTTP/WebSocket doubles.
  • Not applicable: none. Every source-diff file has a target counterpart and is ported.

Target-only support files: generated API report plugins/elevenlabs/etc/agents-plugin-elevenlabs.api.md and minor changeset .changeset/bright-dialogue-streams.md.

Validation

  • pnpm test plugins/elevenlabs (36 passed, 2 credential-gated skipped)
  • pnpm build
  • pnpm lint
  • pnpm format:check
  • pnpm --filter @livekit/agents-plugin-elevenlabs api:check

cue-cli runtime validation was not run because ELEVEN_API_KEY is unavailable in the environment. The source-equivalent dialogue protocol behavior is covered by local HTTP/WebSocket tests.


Ported from livekit/agents#6838

Original PR description

Summary

Adds streaming support for eleven_v3 / eleven_v3_conversational by routing them through ElevenLabs' text-to-dialogue API — the regular text-to-speech streaming websocket (multi-stream-input) rejects these models outright today, so SynthesizeStream currently fails for them.

  • ChunkedStream now posts to /v1/text-to-dialogue/stream for these models.
  • New _DialogueConnection mirrors the existing _Connection (same shared-connection, multi-context design for barge-in) but speaks text-to-dialogue's multi-stream-input protocol instead.
  • Added eleven_v3_conversational to TTSModels (eleven_v3 was already listed but non-functional for streaming).

Quickstart guide here
Websocket documentation here

Testing

  • Lint/format/mypy all pass
  • 16 new unit tests (26/26 total). Verified against the live API for both models — ChunkedStream and SynthesizeStream, including concurrent streams to simulate barge-in.

@rosetta-livekit-bot
rosetta-livekit-bot Bot requested a review from a team as a code owner August 26, 2026 03:57
@changeset-bot

changeset-bot Bot commented Aug 26, 2026

Copy link
Copy Markdown

🦋 Changeset detected

Latest commit: 969893d

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-elevenlabs Major
@livekit/agents 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-fishaudio Major
@livekit/agents-plugin-google 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-openai Major
@livekit/agents-plugin-perplexity Major
@livekit/agents-plugin-phonic 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-plugin-trugen Major
@livekit/agents-plugin-xai Major
@livekit/agents-plugins-test 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 2 potential issues.

View 1 additional finding in Devin Review. (Configure)

Open in Devin Review

Comment on lines +751 to +753
if (ctx.timeoutTimer) {
clearTimeout(ctx.timeoutTimer);
}

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.

🟡 Response timeout disabled after first audio chunk

When audio arrives, clearTimeout clears the per-context watchdog but leaves ctx.timeoutTimer set, so the arm guard at tts.ts never re-arms it on later content sends. If the provider stalls mid-stream after some audio, the stream waits forever instead of timing out.

Suggested change
if (ctx.timeoutTimer) {
clearTimeout(ctx.timeoutTimer);
}
if (ctx.timeoutTimer) {
clearTimeout(ctx.timeoutTimer);
ctx.timeoutTimer = undefined;
}
Open in Devin Review

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

Comment on lines +625 to +629
: {
context_id: contextId,
error: data.error,
'lk.pii.data': data,
},

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.

🟨 Provider error logged under untagged key

The non-dialogue error branch logs the provider error string as error: data.error, a key with no dot-delimited pii segment. REVIEW.md requires provider response payloads to use an lk.pii.<name> marker; the sibling dialogue branch already uses 'lk.pii.error'. This value reaches logs unredacted.

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.

0 participants