Skip to content

Add Respeecher TTS plugin#1646

Open
rosetta-livekit-bot[bot] wants to merge 1 commit into
mainfrom
horsy-slinging-outrider
Open

Add Respeecher TTS plugin#1646
rosetta-livekit-bot[bot] wants to merge 1 commit into
mainfrom
horsy-slinging-outrider

Conversation

@rosetta-livekit-bot
Copy link
Copy Markdown
Contributor

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

Description

This PR adds a new TTS plugin for LiveKit Agents, integrating Respeecher’s synthesis and streaming API. It currently supports the English voice model and the Ukrainian voice models.

Why Respeecher?

Respeecher’s technology is notable not just for voice quality but also for its ethical design and safeguards. This integration offers:

  • Professional-grade synthesis quality powered by advanced voice modeling.
  • Informed consent protocols: Respeecher requires explicit permission from voice owners before using their vocal data, ensuring ethical and lawful use.
  • Commitment to industry ethics: Respeecher is a founding partner in the Partnership on AI’s Responsible Practices for Synthetic Media, actively promoting standards for accountability, privacy, and transparency in synthetic media.

@changeset-bot
Copy link
Copy Markdown

changeset-bot Bot commented May 29, 2026

🦋 Changeset detected

Latest commit: 45c4539

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

This PR includes changesets to release 34 packages
Name Type
@livekit/agents-plugin-respeecher Patch
@livekit/agents Patch
@livekit/agents-plugin-anam Patch
@livekit/agents-plugin-assemblyai 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-elevenlabs Patch
@livekit/agents-plugin-fishaudio Patch
@livekit/agents-plugin-google Patch
@livekit/agents-plugin-hedra Patch
@livekit/agents-plugin-hume Patch
@livekit/agents-plugin-inworld Patch
@livekit/agents-plugin-lemonslice Patch
@livekit/agents-plugin-liveavatar Patch
@livekit/agents-plugin-livekit 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-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-tavus Patch
@livekit/agents-plugin-trugen Patch
@livekit/agents-plugin-xai Patch
@livekit/agents-plugins-test 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

Copy link
Copy Markdown
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

Comment on lines +378 to +381
} finally {
timeout.cleanup();
this.queue.close();
}
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.

🔴 ChunkedStream.run() closes queue in finally block, breaking retry behavior

The finally block at line 380 unconditionally calls this.queue.close(). The base class ChunkedStream (agents/src/tts/tts.ts:567) wraps run() inside mainTask().finally(() => this.queue.close()) and retries run() on retryable errors (like APITimeoutError or APIConnectionError, which are retryable by default per agents/src/_exceptions.ts:119,136). When a retry occurs, sendLastFrame checks !queue.closed and silently drops all audio frames, so the retry "succeeds" but produces zero output. The MiniMax plugin (plugins/minimax/src/tts.ts:429-432) explicitly documents this pitfall: "Do NOT close this.queue here. The base class wraps run() in mainTask().finally(() => this.queue.close()) and retries run() on retryable errors."

Suggested change
} finally {
timeout.cleanup();
this.queue.close();
}
} finally {
timeout.cleanup();
}
Open in Devin Review

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

Comment on lines +460 to +462
const markInputEnded = sendTask().then(() => {
inputEnded = true;
});
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.

🟡 Unhandled promise rejection from markInputEnded when WebSocket closes unexpectedly

In recvTask, markInputEnded = sendTask().then(...) is created at line 460. If the inner await new Promise<void>() at line 464 rejects first (e.g., due to an unexpected WebSocket close at line 523-529), the function exits before reaching await markInputEnded at line 537. Meanwhile, sendTask() will eventually call sendWsJson on the closed WebSocket, which throws APIConnectionError (since signal.aborted is false in the non-abort close scenario). This makes markInputEnded a rejected promise with no handler, triggering Node.js UnhandledPromiseRejection warnings or process crashes depending on configuration.

Prompt for agents
In SynthesizeStream.run()'s recvTask (plugins/respeecher/src/tts.ts), the markInputEnded promise created from sendTask().then(...) at line 460 can become an unhandled rejected promise if the inner await new Promise<void>() rejects before await markInputEnded is reached at line 537. To fix this, attach a no-op .catch() to markInputEnded to suppress the unhandled rejection warning, or restructure to ensure markInputEnded is always awaited (e.g., using Promise.allSettled or try/finally around the inner await). For example: const markInputEnded = sendTask().then(() => { inputEnded = true; }).catch(() => {}); -- or use a finally block that always awaits markInputEnded regardless of whether the inner promise rejected.
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