feat(speechify): add Speechify TTS plugin#1968
Open
luke-speechify wants to merge 10 commits into
Open
Conversation
🦋 Changeset detectedLatest commit: d861665 The changes in this PR will be included in the next version bump. This PR includes changesets to release 37 packages
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 |
Add @livekit/agents-plugin-speechify: streaming text-to-speech with word-level timestamps via the official @speechify/api SDK. Chunks streamed input into sequential /audio/speech calls, emitting audio and timed transcripts per sentence (streaming + alignedTranscript). Maintained by Speechify (Speechify-AI/livekit-plugin-js).
6869087 to
26fede2
Compare
… drop model cast)
The SDK v3.0.0 renamed the auth constructor option from apiKey to token, and
v3.0.1's GetSpeechRequest.Model union now includes simba-3.0 and simba-3.2
natively so the plugin no longer needs to widen it via a cast.
- plugins/speechify/package.json: @speechify/api ^2.0.0 -> ^3.0.1
- plugins/speechify/src/tts.ts:
- rename public TTSOptions.apiKey -> token; pass through to
new SpeechifyClient({ token, baseUrl })
- update JSDoc + local variable + Omit-union for updateOptions
- drop the `opts.model as Speechify.GetSpeechRequest.Model` cast + comment;
v3.0.1's Model union already covers simba-english / simba-multilingual /
simba-3.0 / simba-3.2
- pnpm-lock.yaml: refreshed
Addresses Devin review feedback on livekit#1968. The base class SynthesizeStream.monitorMetrics gates all TTS metric emission on this.#startedHrTime being set, which only happens via markStarted(). Without it, TTFB, duration, and character-count metrics were silently dropped for all Speechify streaming synthesis, and USERDATA_TTS_STARTED_TIME never got stamped on frames so downstream transcription timing was degraded. Matches the elevenlabs / cartesia / resemble / deepgram plugin pattern.
Addresses Devin review findings on livekit#1968 that both Chunked and Synthesize streams close `this.queue` in their run() catch blocks before re-throwing. The base class already closes the queue in its own `finally` after run() returns, and it retries run() on retryable APIErrors - so closing on error made the next retry's `queue.put(...)` throw 'Queue is closed', turning transient network errors into permanent failures. Matches the guard the minimax plugin documents on its own catch.
Addresses Devin review finding on livekit#1968: calling `sentenceStream.close()` immediately after `endInput()` closes the tokenizer's output queue before its internal async loop has a chance to flush the last buffered sentence, so the final `queue.put(token)` throws 'Queue is closed' and consume() never sees the tail. Result: the last sentence of a TTS response could be silently dropped. Matches the neuphonic plugin pattern: only call endInput(), let the tokenizer close its own output queue when its input drains.
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Summary
Adds
@livekit/agents-plugin-speechify— a Speechify TTS plugin for Agents JS with streaming audio and word-level timestamps, built on Speechify's official@speechify/apiSDK.There is currently no Speechify plugin in agents-js; this adds one following the existing plugin conventions (mirrors the
cartesiapackage layout).Details
Speechify's word-level speech marks are only returned by the batch
/audio/speechendpoint, soSynthesizeStreamchunks streamed input into per-sentence sequential/audio/speechcalls, emitting audio and aligned word timestamps as each sentence completes — near-streaming time-to-first-audio plusalignedTranscript.capabilities: { streaming: true, alignedTranscript: true }SynthesizeStream(sentence-chunked, cumulative word-mark offsets) +ChunkedStream(one-shot)@speechify/apivendor SDKturbo.jsonenv allowlist (SPEECHIFY_API_KEY)Maintenance & distribution
This plugin is maintained by Speechify and distributed by LiveKit as part of
livekit/agents-js. Speechify keeps a mirror of the plugin source at Speechify-AI/livekit-plugin-js for maintenance and issue triage, and proposes changes upstream here. The canonical, user-installable package is@livekit/agents-plugin-speechify, published by LiveKit — there is no separate Speechify-published package.Verification
pnpm build(ESM + CJS + type declarations) succeedspnpm lint(eslint/prettier/turbo) cleantsc --noEmitcleanHappy to adjust to match maintainer preferences.