@tanstack/ai-elevenlabs 0.4.2 → 0.6.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/esm/adapters/audio.d.ts +2 -2
- package/dist/esm/adapters/audio.js +3 -3
- package/dist/esm/adapters/audio.js.map +1 -1
- package/dist/esm/adapters/speech.d.ts +14 -2
- package/dist/esm/adapters/speech.js +162 -13
- package/dist/esm/adapters/speech.js.map +1 -1
- package/dist/esm/adapters/voice.d.ts +85 -0
- package/dist/esm/adapters/voice.js +154 -0
- package/dist/esm/adapters/voice.js.map +1 -0
- package/dist/esm/index.d.ts +2 -1
- package/dist/esm/index.js +3 -2
- package/dist/esm/model-meta.d.ts +35 -10
- package/dist/esm/model-meta.js +38 -8
- package/dist/esm/model-meta.js.map +1 -1
- package/package.json +5 -5
- package/src/adapters/audio.ts +4 -3
- package/src/adapters/speech.ts +241 -25
- package/src/adapters/voice.ts +299 -0
- package/src/index.ts +13 -0
- package/src/model-meta.ts +61 -13
|
@@ -32,7 +32,7 @@ interface CommonAudioOptions {
|
|
|
32
32
|
outputFormat?: ElevenLabsOutputFormat;
|
|
33
33
|
}
|
|
34
34
|
/**
|
|
35
|
-
* Provider options for music generation (`
|
|
35
|
+
* Provider options for music generation (`music_v*`).
|
|
36
36
|
*/
|
|
37
37
|
export interface ElevenLabsMusicProviderOptions extends CommonAudioOptions {
|
|
38
38
|
/** Structured composition plan. Mutually exclusive with `prompt`/`duration`. */
|
|
@@ -65,7 +65,7 @@ export type ElevenLabsAudioProviderOptions = (ElevenLabsMusicProviderOptions & E
|
|
|
65
65
|
*
|
|
66
66
|
* @example
|
|
67
67
|
* ```ts
|
|
68
|
-
* const music = elevenlabsAudio('
|
|
68
|
+
* const music = elevenlabsAudio('music_v2_5')
|
|
69
69
|
* await generateAudio({ adapter: music, prompt: 'lo-fi beat', duration: 15 })
|
|
70
70
|
*
|
|
71
71
|
* const sfx = elevenlabsAudio('eleven_text_to_sound_v2')
|
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import { arrayBufferToBase64, createElevenLabsClient, generateId, parseOutputFormat, readStreamToArrayBuffer } from "../utils/client.js";
|
|
2
|
-
import { isElevenLabsMusicModel, isElevenLabsSoundEffectsModel } from "../model-meta.js";
|
|
2
|
+
import { ELEVENLABS_AUDIO_MODELS, isElevenLabsMusicModel, isElevenLabsSoundEffectsModel } from "../model-meta.js";
|
|
3
3
|
import { BaseAudioAdapter } from "@tanstack/ai/adapters";
|
|
4
4
|
//#region src/adapters/audio.ts
|
|
5
5
|
/**
|
|
@@ -9,7 +9,7 @@ import { BaseAudioAdapter } from "@tanstack/ai/adapters";
|
|
|
9
9
|
*
|
|
10
10
|
* @example
|
|
11
11
|
* ```ts
|
|
12
|
-
* const music = elevenlabsAudio('
|
|
12
|
+
* const music = elevenlabsAudio('music_v2_5')
|
|
13
13
|
* await generateAudio({ adapter: music, prompt: 'lo-fi beat', duration: 15 })
|
|
14
14
|
*
|
|
15
15
|
* const sfx = elevenlabsAudio('eleven_text_to_sound_v2')
|
|
@@ -32,7 +32,7 @@ var ElevenLabsAudioAdapter = class extends BaseAudioAdapter {
|
|
|
32
32
|
try {
|
|
33
33
|
if (isElevenLabsMusicModel(this.model)) return await this.runMusic(options);
|
|
34
34
|
if (isElevenLabsSoundEffectsModel(this.model)) return await this.runSoundEffects(options);
|
|
35
|
-
throw new Error(`Unsupported ElevenLabs audio model "${this.model}". Expected one of:
|
|
35
|
+
throw new Error(`Unsupported ElevenLabs audio model "${this.model}". Expected one of: ${ELEVENLABS_AUDIO_MODELS.join(", ")}.`);
|
|
36
36
|
} catch (error) {
|
|
37
37
|
logger.errors("elevenlabs.generateAudio fatal", {
|
|
38
38
|
error,
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"audio.js","names":[],"sources":["../../../src/adapters/audio.ts"],"sourcesContent":["import { BaseAudioAdapter } from '@tanstack/ai/adapters'\nimport {\n arrayBufferToBase64,\n createElevenLabsClient,\n generateId,\n parseOutputFormat,\n readStreamToArrayBuffer,\n} from '../utils/client'\nimport {\n isElevenLabsMusicModel,\n isElevenLabsSoundEffectsModel,\n} from '../model-meta'\nimport type { ElevenLabsClient } from '@elevenlabs/elevenlabs-js'\nimport type {\n AudioGenerationOptions,\n AudioGenerationResult,\n} from '@tanstack/ai'\nimport type { ElevenLabsClientConfig } from '../utils/client'\nimport type {\n ElevenLabsAudioModel,\n ElevenLabsMusicModel,\n ElevenLabsOutputFormat,\n ElevenLabsSoundEffectsModel,\n} from '../model-meta'\n\n/**\n * Structured composition plan for ElevenLabs music generation. Mutually\n * exclusive with a free-form `prompt` on the `generateAudio()` call — when\n * supplied, `prompt` is ignored by ElevenLabs.\n *\n * We mirror the SDK's camelCase naming. Lengths are in milliseconds.\n * @see https://elevenlabs.io/docs/api-reference/music/compose\n */\nexport interface ElevenLabsMusicCompositionPlan {\n /** Positive global style descriptors (mood, instruments, tempo, …). */\n positiveGlobalStyles?: Array<string>\n /** Negative global style descriptors — styles to avoid. */\n negativeGlobalStyles?: Array<string>\n /** Section definitions (verse/chorus/bridge/…) with local style hints. */\n sections?: Array<{\n sectionName: string\n positiveLocalStyles?: Array<string>\n negativeLocalStyles?: Array<string>\n durationMs?: number\n lines?: Array<string>\n }>\n}\n\n/**\n * Provider options common to all ElevenLabs audio endpoints.\n */\ninterface CommonAudioOptions {\n /** Output audio format. Defaults to `mp3_44100_128`. */\n outputFormat?: ElevenLabsOutputFormat\n}\n\n/**\n * Provider options for music generation (`music_v1`).\n */\nexport interface ElevenLabsMusicProviderOptions extends CommonAudioOptions {\n /** Structured composition plan. Mutually exclusive with `prompt`/`duration`. */\n compositionPlan?: ElevenLabsMusicCompositionPlan\n /** Deterministic sampling seed (incompatible with `prompt`). */\n seed?: number\n /** Force the output to be purely instrumental (prompt-mode only). */\n forceInstrumental?: boolean\n /** Strictly respect section durations in `compositionPlan`. */\n respectSectionsDurations?: boolean\n}\n\n/**\n * Provider options for sound-effect generation (`eleven_text_to_sound_v*`).\n */\nexport interface ElevenLabsSoundEffectsProviderOptions extends CommonAudioOptions {\n /** Prompt influence, 0..1. Default 0.3. Higher = more prompt adherence. */\n promptInfluence?: number\n /** Generate a loopable SFX (v2 only). */\n loop?: boolean\n}\n\n/**\n * Union of per-model provider options. We keep both branches on one type so\n * the adapter stays tree-shakeable; callers narrow by model at the factory.\n */\nexport type ElevenLabsAudioProviderOptions =\n | (ElevenLabsMusicProviderOptions & ElevenLabsSoundEffectsProviderOptions)\n | ElevenLabsMusicProviderOptions\n | ElevenLabsSoundEffectsProviderOptions\n\n/**\n * ElevenLabs audio generation adapter. Dispatches to music or SFX endpoints\n * based on the model id. Music → `client.music.compose`, SFX →\n * `client.textToSoundEffects.convert`.\n *\n * @example\n * ```ts\n * const music = elevenlabsAudio('music_v1')\n * await generateAudio({ adapter: music, prompt: 'lo-fi beat', duration: 15 })\n *\n * const sfx = elevenlabsAudio('eleven_text_to_sound_v2')\n * await generateAudio({ adapter: sfx, prompt: 'glass shattering', duration: 3 })\n * ```\n */\nexport class ElevenLabsAudioAdapter<\n TModel extends ElevenLabsAudioModel,\n> extends BaseAudioAdapter<TModel, ElevenLabsAudioProviderOptions> {\n readonly name = 'elevenlabs' as const\n\n private readonly client: ElevenLabsClient\n\n constructor(model: TModel, config?: ElevenLabsClientConfig) {\n super(model, config ?? {})\n this.client = createElevenLabsClient(config)\n }\n\n async generateAudio(\n options: AudioGenerationOptions<ElevenLabsAudioProviderOptions>,\n ): Promise<AudioGenerationResult> {\n const { logger } = options\n logger.request(\n `activity=generateAudio provider=elevenlabs model=${this.model}`,\n { provider: 'elevenlabs', model: this.model },\n )\n try {\n if (isElevenLabsMusicModel(this.model)) {\n return await this.runMusic(options)\n }\n if (isElevenLabsSoundEffectsModel(this.model)) {\n return await this.runSoundEffects(options)\n }\n throw new Error(\n `Unsupported ElevenLabs audio model \"${this.model}\". Expected one of: music_v1, eleven_text_to_sound_v2, eleven_text_to_sound_v1.`,\n )\n } catch (error) {\n logger.errors('elevenlabs.generateAudio fatal', {\n error,\n source: 'elevenlabs.generateAudio',\n })\n throw error\n }\n }\n\n private async runMusic(\n options: AudioGenerationOptions<ElevenLabsAudioProviderOptions>,\n ): Promise<AudioGenerationResult> {\n // Gated by isElevenLabsMusicModel() in generateAudio().\n const modelId = this.model as ElevenLabsMusicModel\n const music = (options.modelOptions ?? {}) as ElevenLabsMusicProviderOptions\n const outputFormat = music.outputFormat\n\n const stream = await this.client.music.compose({\n modelId,\n ...(options.prompt && !music.compositionPlan\n ? { prompt: options.prompt }\n : {}),\n ...(music.compositionPlan\n ? { compositionPlan: toMusicPrompt(music.compositionPlan) }\n : {}),\n ...(options.duration != null && !music.compositionPlan\n ? { musicLengthMs: Math.round(options.duration * 1000) }\n : {}),\n ...(outputFormat ? { outputFormat } : {}),\n ...(music.seed != null ? { seed: music.seed } : {}),\n ...(music.forceInstrumental != null\n ? { forceInstrumental: music.forceInstrumental }\n : {}),\n ...(music.respectSectionsDurations != null\n ? { respectSectionsDurations: music.respectSectionsDurations }\n : {}),\n })\n\n return this.finalize(stream, outputFormat, options.duration)\n }\n\n private async runSoundEffects(\n options: AudioGenerationOptions<ElevenLabsAudioProviderOptions>,\n ): Promise<AudioGenerationResult> {\n // Gated by isElevenLabsSoundEffectsModel() in generateAudio().\n const modelId = this.model as ElevenLabsSoundEffectsModel\n const sfx = (options.modelOptions ??\n {}) as ElevenLabsSoundEffectsProviderOptions\n const outputFormat = sfx.outputFormat\n\n const stream = await this.client.textToSoundEffects.convert({\n text: options.prompt,\n modelId,\n ...(options.duration != null\n ? { durationSeconds: options.duration }\n : {}),\n ...(outputFormat ? { outputFormat } : {}),\n ...(sfx.promptInfluence != null\n ? { promptInfluence: sfx.promptInfluence }\n : {}),\n ...(sfx.loop != null ? { loop: sfx.loop } : {}),\n })\n\n return this.finalize(stream, outputFormat, options.duration)\n }\n\n private async finalize(\n stream: ReadableStream<Uint8Array>,\n outputFormat: ElevenLabsOutputFormat | undefined,\n duration: number | undefined,\n ): Promise<AudioGenerationResult> {\n const buffer = await readStreamToArrayBuffer(stream)\n const base64 = arrayBufferToBase64(buffer)\n const { contentType } = parseOutputFormat(outputFormat)\n return {\n id: generateId(this.name),\n model: this.model,\n audio: {\n b64Json: base64,\n contentType,\n ...(duration != null ? { duration } : {}),\n },\n }\n }\n\n protected override generateId(): string {\n return generateId(this.name)\n }\n}\n\nfunction toMusicPrompt(plan: ElevenLabsMusicCompositionPlan) {\n return {\n positiveGlobalStyles: plan.positiveGlobalStyles ?? [],\n negativeGlobalStyles: plan.negativeGlobalStyles ?? [],\n sections: (plan.sections ?? []).map((section) => ({\n sectionName: section.sectionName,\n positiveLocalStyles: section.positiveLocalStyles ?? [],\n negativeLocalStyles: section.negativeLocalStyles ?? [],\n durationMs: section.durationMs ?? 10000,\n lines: section.lines ?? [],\n })),\n }\n}\n\n/**\n * Create an ElevenLabs audio adapter using `ELEVENLABS_API_KEY` from env.\n */\nexport function elevenlabsAudio<TModel extends ElevenLabsAudioModel>(\n model: TModel,\n config?: ElevenLabsClientConfig,\n): ElevenLabsAudioAdapter<TModel> {\n return new ElevenLabsAudioAdapter(model, config)\n}\n\n/**\n * Create an ElevenLabs audio adapter with an explicit API key.\n */\nexport function createElevenLabsAudio<TModel extends ElevenLabsAudioModel>(\n model: TModel,\n apiKey: string,\n config?: Omit<ElevenLabsClientConfig, 'apiKey'>,\n): ElevenLabsAudioAdapter<TModel> {\n return new ElevenLabsAudioAdapter(model, { apiKey, ...config })\n}\n"],"mappings":"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"}
|
|
1
|
+
{"version":3,"file":"audio.js","names":[],"sources":["../../../src/adapters/audio.ts"],"sourcesContent":["import { BaseAudioAdapter } from '@tanstack/ai/adapters'\nimport {\n arrayBufferToBase64,\n createElevenLabsClient,\n generateId,\n parseOutputFormat,\n readStreamToArrayBuffer,\n} from '../utils/client'\nimport {\n ELEVENLABS_AUDIO_MODELS,\n isElevenLabsMusicModel,\n isElevenLabsSoundEffectsModel,\n} from '../model-meta'\nimport type { ElevenLabsClient } from '@elevenlabs/elevenlabs-js'\nimport type {\n AudioGenerationOptions,\n AudioGenerationResult,\n} from '@tanstack/ai'\nimport type { ElevenLabsClientConfig } from '../utils/client'\nimport type {\n ElevenLabsAudioModel,\n ElevenLabsMusicModel,\n ElevenLabsOutputFormat,\n ElevenLabsSoundEffectsModel,\n} from '../model-meta'\n\n/**\n * Structured composition plan for ElevenLabs music generation. Mutually\n * exclusive with a free-form `prompt` on the `generateAudio()` call — when\n * supplied, `prompt` is ignored by ElevenLabs.\n *\n * We mirror the SDK's camelCase naming. Lengths are in milliseconds.\n * @see https://elevenlabs.io/docs/api-reference/music/compose\n */\nexport interface ElevenLabsMusicCompositionPlan {\n /** Positive global style descriptors (mood, instruments, tempo, …). */\n positiveGlobalStyles?: Array<string>\n /** Negative global style descriptors — styles to avoid. */\n negativeGlobalStyles?: Array<string>\n /** Section definitions (verse/chorus/bridge/…) with local style hints. */\n sections?: Array<{\n sectionName: string\n positiveLocalStyles?: Array<string>\n negativeLocalStyles?: Array<string>\n durationMs?: number\n lines?: Array<string>\n }>\n}\n\n/**\n * Provider options common to all ElevenLabs audio endpoints.\n */\ninterface CommonAudioOptions {\n /** Output audio format. Defaults to `mp3_44100_128`. */\n outputFormat?: ElevenLabsOutputFormat\n}\n\n/**\n * Provider options for music generation (`music_v*`).\n */\nexport interface ElevenLabsMusicProviderOptions extends CommonAudioOptions {\n /** Structured composition plan. Mutually exclusive with `prompt`/`duration`. */\n compositionPlan?: ElevenLabsMusicCompositionPlan\n /** Deterministic sampling seed (incompatible with `prompt`). */\n seed?: number\n /** Force the output to be purely instrumental (prompt-mode only). */\n forceInstrumental?: boolean\n /** Strictly respect section durations in `compositionPlan`. */\n respectSectionsDurations?: boolean\n}\n\n/**\n * Provider options for sound-effect generation (`eleven_text_to_sound_v*`).\n */\nexport interface ElevenLabsSoundEffectsProviderOptions extends CommonAudioOptions {\n /** Prompt influence, 0..1. Default 0.3. Higher = more prompt adherence. */\n promptInfluence?: number\n /** Generate a loopable SFX (v2 only). */\n loop?: boolean\n}\n\n/**\n * Union of per-model provider options. We keep both branches on one type so\n * the adapter stays tree-shakeable; callers narrow by model at the factory.\n */\nexport type ElevenLabsAudioProviderOptions =\n | (ElevenLabsMusicProviderOptions & ElevenLabsSoundEffectsProviderOptions)\n | ElevenLabsMusicProviderOptions\n | ElevenLabsSoundEffectsProviderOptions\n\n/**\n * ElevenLabs audio generation adapter. Dispatches to music or SFX endpoints\n * based on the model id. Music → `client.music.compose`, SFX →\n * `client.textToSoundEffects.convert`.\n *\n * @example\n * ```ts\n * const music = elevenlabsAudio('music_v2_5')\n * await generateAudio({ adapter: music, prompt: 'lo-fi beat', duration: 15 })\n *\n * const sfx = elevenlabsAudio('eleven_text_to_sound_v2')\n * await generateAudio({ adapter: sfx, prompt: 'glass shattering', duration: 3 })\n * ```\n */\nexport class ElevenLabsAudioAdapter<\n TModel extends ElevenLabsAudioModel,\n> extends BaseAudioAdapter<TModel, ElevenLabsAudioProviderOptions> {\n readonly name = 'elevenlabs' as const\n\n private readonly client: ElevenLabsClient\n\n constructor(model: TModel, config?: ElevenLabsClientConfig) {\n super(model, config ?? {})\n this.client = createElevenLabsClient(config)\n }\n\n async generateAudio(\n options: AudioGenerationOptions<ElevenLabsAudioProviderOptions>,\n ): Promise<AudioGenerationResult> {\n const { logger } = options\n logger.request(\n `activity=generateAudio provider=elevenlabs model=${this.model}`,\n { provider: 'elevenlabs', model: this.model },\n )\n try {\n if (isElevenLabsMusicModel(this.model)) {\n return await this.runMusic(options)\n }\n if (isElevenLabsSoundEffectsModel(this.model)) {\n return await this.runSoundEffects(options)\n }\n throw new Error(\n `Unsupported ElevenLabs audio model \"${this.model}\". Expected one of: ${ELEVENLABS_AUDIO_MODELS.join(', ')}.`,\n )\n } catch (error) {\n logger.errors('elevenlabs.generateAudio fatal', {\n error,\n source: 'elevenlabs.generateAudio',\n })\n throw error\n }\n }\n\n private async runMusic(\n options: AudioGenerationOptions<ElevenLabsAudioProviderOptions>,\n ): Promise<AudioGenerationResult> {\n // Gated by isElevenLabsMusicModel() in generateAudio().\n const modelId = this.model as ElevenLabsMusicModel\n const music = (options.modelOptions ?? {}) as ElevenLabsMusicProviderOptions\n const outputFormat = music.outputFormat\n\n const stream = await this.client.music.compose({\n modelId,\n ...(options.prompt && !music.compositionPlan\n ? { prompt: options.prompt }\n : {}),\n ...(music.compositionPlan\n ? { compositionPlan: toMusicPrompt(music.compositionPlan) }\n : {}),\n ...(options.duration != null && !music.compositionPlan\n ? { musicLengthMs: Math.round(options.duration * 1000) }\n : {}),\n ...(outputFormat ? { outputFormat } : {}),\n ...(music.seed != null ? { seed: music.seed } : {}),\n ...(music.forceInstrumental != null\n ? { forceInstrumental: music.forceInstrumental }\n : {}),\n ...(music.respectSectionsDurations != null\n ? { respectSectionsDurations: music.respectSectionsDurations }\n : {}),\n })\n\n return this.finalize(stream, outputFormat, options.duration)\n }\n\n private async runSoundEffects(\n options: AudioGenerationOptions<ElevenLabsAudioProviderOptions>,\n ): Promise<AudioGenerationResult> {\n // Gated by isElevenLabsSoundEffectsModel() in generateAudio().\n const modelId = this.model as ElevenLabsSoundEffectsModel\n const sfx = (options.modelOptions ??\n {}) as ElevenLabsSoundEffectsProviderOptions\n const outputFormat = sfx.outputFormat\n\n const stream = await this.client.textToSoundEffects.convert({\n text: options.prompt,\n modelId,\n ...(options.duration != null\n ? { durationSeconds: options.duration }\n : {}),\n ...(outputFormat ? { outputFormat } : {}),\n ...(sfx.promptInfluence != null\n ? { promptInfluence: sfx.promptInfluence }\n : {}),\n ...(sfx.loop != null ? { loop: sfx.loop } : {}),\n })\n\n return this.finalize(stream, outputFormat, options.duration)\n }\n\n private async finalize(\n stream: ReadableStream<Uint8Array>,\n outputFormat: ElevenLabsOutputFormat | undefined,\n duration: number | undefined,\n ): Promise<AudioGenerationResult> {\n const buffer = await readStreamToArrayBuffer(stream)\n const base64 = arrayBufferToBase64(buffer)\n const { contentType } = parseOutputFormat(outputFormat)\n return {\n id: generateId(this.name),\n model: this.model,\n audio: {\n b64Json: base64,\n contentType,\n ...(duration != null ? { duration } : {}),\n },\n }\n }\n\n protected override generateId(): string {\n return generateId(this.name)\n }\n}\n\nfunction toMusicPrompt(plan: ElevenLabsMusicCompositionPlan) {\n return {\n positiveGlobalStyles: plan.positiveGlobalStyles ?? [],\n negativeGlobalStyles: plan.negativeGlobalStyles ?? [],\n sections: (plan.sections ?? []).map((section) => ({\n sectionName: section.sectionName,\n positiveLocalStyles: section.positiveLocalStyles ?? [],\n negativeLocalStyles: section.negativeLocalStyles ?? [],\n durationMs: section.durationMs ?? 10000,\n lines: section.lines ?? [],\n })),\n }\n}\n\n/**\n * Create an ElevenLabs audio adapter using `ELEVENLABS_API_KEY` from env.\n */\nexport function elevenlabsAudio<TModel extends ElevenLabsAudioModel>(\n model: TModel,\n config?: ElevenLabsClientConfig,\n): ElevenLabsAudioAdapter<TModel> {\n return new ElevenLabsAudioAdapter(model, config)\n}\n\n/**\n * Create an ElevenLabs audio adapter with an explicit API key.\n */\nexport function createElevenLabsAudio<TModel extends ElevenLabsAudioModel>(\n model: TModel,\n apiKey: string,\n config?: Omit<ElevenLabsClientConfig, 'apiKey'>,\n): ElevenLabsAudioAdapter<TModel> {\n return new ElevenLabsAudioAdapter(model, { apiKey, ...config })\n}\n"],"mappings":"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"}
|
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import { BaseTTSAdapter } from '@tanstack/ai/adapters';
|
|
2
|
-
import { TTSOptions, TTSResult } from '@tanstack/ai';
|
|
2
|
+
import { ListVoicesOptions, ListVoicesResult, TTSCapabilities, TTSOptions, TTSResult } from '@tanstack/ai';
|
|
3
3
|
import { ElevenLabsClientConfig } from '../utils/client.js';
|
|
4
4
|
import { ElevenLabsOutputFormat, ElevenLabsTTSModel } from '../model-meta.js';
|
|
5
5
|
/**
|
|
@@ -27,7 +27,7 @@ export interface ElevenLabsVoiceSettings {
|
|
|
27
27
|
export interface ElevenLabsSpeechProviderOptions {
|
|
28
28
|
/** ElevenLabs voice ID to synthesize. Required if `generateSpeech().voice` is not set. */
|
|
29
29
|
voiceId?: string;
|
|
30
|
-
/** Output audio format encoded as `codec_samplerate[_bitrate]`. Defaults to `mp3_44100_128`. */
|
|
30
|
+
/** Output audio format encoded as `codec_samplerate[_bitrate]`. Overrides `format`, including WAV wrapping. Defaults to `mp3_44100_128`. */
|
|
31
31
|
outputFormat?: ElevenLabsOutputFormat;
|
|
32
32
|
/** Voice-settings overrides for this request only. */
|
|
33
33
|
voiceSettings?: ElevenLabsVoiceSettings;
|
|
@@ -68,9 +68,21 @@ export interface ElevenLabsSpeechProviderOptions {
|
|
|
68
68
|
*/
|
|
69
69
|
export declare class ElevenLabsSpeechAdapter<TModel extends ElevenLabsTTSModel> extends BaseTTSAdapter<TModel, ElevenLabsSpeechProviderOptions> {
|
|
70
70
|
readonly name: "elevenlabs";
|
|
71
|
+
/**
|
|
72
|
+
* `textToDialogue` accepts up to 10 distinct voice ids, and both endpoints
|
|
73
|
+
* have a `…WithTimestamps` twin that returns character alignment.
|
|
74
|
+
*/
|
|
75
|
+
readonly capabilities: TTSCapabilities;
|
|
71
76
|
private readonly client;
|
|
72
77
|
constructor(model: TModel, config?: ElevenLabsClientConfig);
|
|
73
78
|
generateSpeech(options: TTSOptions<ElevenLabsSpeechProviderOptions>): Promise<TTSResult>;
|
|
79
|
+
/**
|
|
80
|
+
* List the voices this API key can use, via `GET /v1/voices`.
|
|
81
|
+
*
|
|
82
|
+
* The catalog is per-account and grows every time `generateVoice()` saves a
|
|
83
|
+
* voice, so it has to be read at runtime rather than shipped as a const.
|
|
84
|
+
*/
|
|
85
|
+
listVoices(options?: ListVoicesOptions): Promise<ListVoicesResult>;
|
|
74
86
|
protected generateId(): string;
|
|
75
87
|
}
|
|
76
88
|
/**
|
|
@@ -17,6 +17,14 @@ import { BaseTTSAdapter } from "@tanstack/ai/adapters";
|
|
|
17
17
|
*/
|
|
18
18
|
var ElevenLabsSpeechAdapter = class extends BaseTTSAdapter {
|
|
19
19
|
name = "elevenlabs";
|
|
20
|
+
/**
|
|
21
|
+
* `textToDialogue` accepts up to 10 distinct voice ids, and both endpoints
|
|
22
|
+
* have a `…WithTimestamps` twin that returns character alignment.
|
|
23
|
+
*/
|
|
24
|
+
capabilities = {
|
|
25
|
+
maxSpeakers: 10,
|
|
26
|
+
timestamps: true
|
|
27
|
+
};
|
|
20
28
|
client;
|
|
21
29
|
constructor(model, config) {
|
|
22
30
|
super(model, config ?? {});
|
|
@@ -29,33 +37,84 @@ var ElevenLabsSpeechAdapter = class extends BaseTTSAdapter {
|
|
|
29
37
|
model: this.model
|
|
30
38
|
});
|
|
31
39
|
try {
|
|
32
|
-
const voiceId = options.voice ?? options.modelOptions?.voiceId;
|
|
33
|
-
if (!voiceId) throw new Error("ElevenLabs TTS requires a voice. Pass `voice` on generateSpeech() or `voiceId` in modelOptions.");
|
|
34
40
|
const { outputFormat, voiceSettings, languageCode, seed, previousText, nextText, previousRequestIds, nextRequestIds, applyTextNormalization, applyLanguageTextNormalization, optimizeStreamingLatency, enableLogging } = options.modelOptions ?? {};
|
|
35
41
|
const effectiveOutputFormat = outputFormat ?? inferOutputFormatFromResponseFormat(options.format);
|
|
36
|
-
const
|
|
37
|
-
|
|
42
|
+
const wrapAsWav = outputFormat == null && options.format === "wav";
|
|
43
|
+
const { format, contentType } = wrapAsWav ? {
|
|
44
|
+
format: "wav",
|
|
45
|
+
contentType: "audio/wav"
|
|
46
|
+
} : parseOutputFormat(effectiveOutputFormat);
|
|
47
|
+
const encodeAudio = (buffer) => arrayBufferToBase64(wrapAsWav ? wrapPcmAsWav(buffer) : buffer);
|
|
48
|
+
const encodeAudioBase64 = (audioBase64) => wrapAsWav ? encodeAudio(base64ToArrayBuffer(audioBase64)) : audioBase64;
|
|
49
|
+
const base = {
|
|
38
50
|
modelId: this.model,
|
|
39
51
|
...effectiveOutputFormat ? { outputFormat: effectiveOutputFormat } : {},
|
|
40
|
-
...voiceSettings ? { voiceSettings: mapVoiceSettings(voiceSettings, options.speed) } : options.speed != null ? { voiceSettings: { speed: options.speed } } : {},
|
|
41
52
|
...languageCode ? { languageCode } : {},
|
|
42
53
|
...seed != null ? { seed } : {},
|
|
54
|
+
...applyTextNormalization ? { applyTextNormalization } : {}
|
|
55
|
+
};
|
|
56
|
+
if (options.turns) {
|
|
57
|
+
const inputs = options.turns.map((turn) => ({
|
|
58
|
+
text: turn.text,
|
|
59
|
+
voiceId: turn.voice
|
|
60
|
+
}));
|
|
61
|
+
const dialogue = {
|
|
62
|
+
...base,
|
|
63
|
+
inputs,
|
|
64
|
+
...voiceSettings?.stability != null ? { settings: { stability: voiceSettings.stability } } : {}
|
|
65
|
+
};
|
|
66
|
+
if (options.timestamps) {
|
|
67
|
+
const response = await this.client.textToDialogue.convertWithTimestamps(dialogue);
|
|
68
|
+
return {
|
|
69
|
+
id: generateId(this.name),
|
|
70
|
+
model: this.model,
|
|
71
|
+
audio: encodeAudioBase64(response.audioBase64),
|
|
72
|
+
format,
|
|
73
|
+
contentType,
|
|
74
|
+
...toAlignmentFields(response.alignment, response.voiceSegments)
|
|
75
|
+
};
|
|
76
|
+
}
|
|
77
|
+
const stream = await this.client.textToDialogue.convert(dialogue);
|
|
78
|
+
return {
|
|
79
|
+
id: generateId(this.name),
|
|
80
|
+
model: this.model,
|
|
81
|
+
audio: encodeAudio(await readStreamToArrayBuffer(stream)),
|
|
82
|
+
format,
|
|
83
|
+
contentType
|
|
84
|
+
};
|
|
85
|
+
}
|
|
86
|
+
const voiceId = options.voice ?? options.modelOptions?.voiceId;
|
|
87
|
+
if (!voiceId) throw new Error("ElevenLabs TTS requires a voice. Pass `voice` on generateSpeech() or `voiceId` in modelOptions.");
|
|
88
|
+
const single = {
|
|
89
|
+
...base,
|
|
90
|
+
text: options.text,
|
|
91
|
+
...voiceSettings ? { voiceSettings: mapVoiceSettings(voiceSettings, options.speed) } : options.speed != null ? { voiceSettings: { speed: options.speed } } : {},
|
|
43
92
|
...previousText ? { previousText } : {},
|
|
44
93
|
...nextText ? { nextText } : {},
|
|
45
94
|
...previousRequestIds ? { previousRequestIds } : {},
|
|
46
95
|
...nextRequestIds ? { nextRequestIds } : {},
|
|
47
|
-
...applyTextNormalization ? { applyTextNormalization } : {},
|
|
48
96
|
...applyLanguageTextNormalization != null ? { applyLanguageTextNormalization } : {},
|
|
49
|
-
...optimizeStreamingLatency != null ? { optimizeStreamingLatency } : {},
|
|
50
97
|
...enableLogging != null ? { enableLogging } : {}
|
|
98
|
+
};
|
|
99
|
+
if (options.timestamps) {
|
|
100
|
+
const response = await this.client.textToSpeech.convertWithTimestamps(voiceId, single);
|
|
101
|
+
return {
|
|
102
|
+
id: generateId(this.name),
|
|
103
|
+
model: this.model,
|
|
104
|
+
audio: encodeAudioBase64(response.audioBase64),
|
|
105
|
+
format,
|
|
106
|
+
contentType,
|
|
107
|
+
...toAlignmentFields(response.alignment, void 0)
|
|
108
|
+
};
|
|
109
|
+
}
|
|
110
|
+
const stream = await this.client.textToSpeech.convert(voiceId, {
|
|
111
|
+
...single,
|
|
112
|
+
...optimizeStreamingLatency != null ? { optimizeStreamingLatency } : {}
|
|
51
113
|
});
|
|
52
|
-
const buffer = await readStreamToArrayBuffer(stream);
|
|
53
|
-
const base64 = arrayBufferToBase64(buffer);
|
|
54
|
-
const { format, contentType } = parseOutputFormat(effectiveOutputFormat);
|
|
55
114
|
return {
|
|
56
115
|
id: generateId(this.name),
|
|
57
116
|
model: this.model,
|
|
58
|
-
audio:
|
|
117
|
+
audio: encodeAudio(await readStreamToArrayBuffer(stream)),
|
|
59
118
|
format,
|
|
60
119
|
contentType
|
|
61
120
|
};
|
|
@@ -67,10 +126,43 @@ var ElevenLabsSpeechAdapter = class extends BaseTTSAdapter {
|
|
|
67
126
|
throw error;
|
|
68
127
|
}
|
|
69
128
|
}
|
|
129
|
+
/**
|
|
130
|
+
* List the voices this API key can use, via `GET /v1/voices`.
|
|
131
|
+
*
|
|
132
|
+
* The catalog is per-account and grows every time `generateVoice()` saves a
|
|
133
|
+
* voice, so it has to be read at runtime rather than shipped as a const.
|
|
134
|
+
*/
|
|
135
|
+
async listVoices(options) {
|
|
136
|
+
const voices = (await this.client.voices.getAll({}, options?.abortSignal ? { abortSignal: options.abortSignal } : {})).voices.map(toCatalogVoice);
|
|
137
|
+
const origins = options?.origins;
|
|
138
|
+
return { voices: origins ? voices.filter((voice) => voice.origin && origins.includes(voice.origin)) : voices };
|
|
139
|
+
}
|
|
70
140
|
generateId() {
|
|
71
141
|
return generateId(this.name);
|
|
72
142
|
}
|
|
73
143
|
};
|
|
144
|
+
/**
|
|
145
|
+
* Map the ElevenLabs timestamp payload onto the core `alignment` / `segments`
|
|
146
|
+
* fields. `voiceSegments` only comes back from the dialogue endpoint; its
|
|
147
|
+
* character indices slice the alignment into per-turn text.
|
|
148
|
+
*/
|
|
149
|
+
function toAlignmentFields(alignment, voiceSegments) {
|
|
150
|
+
const fields = {};
|
|
151
|
+
if (alignment) fields.alignment = {
|
|
152
|
+
unit: "character",
|
|
153
|
+
texts: alignment.characters,
|
|
154
|
+
startSeconds: alignment.characterStartTimesSeconds,
|
|
155
|
+
endSeconds: alignment.characterEndTimesSeconds
|
|
156
|
+
};
|
|
157
|
+
if (voiceSegments && voiceSegments.length > 0) fields.segments = voiceSegments.map((segment) => ({
|
|
158
|
+
startSeconds: segment.startTimeSeconds,
|
|
159
|
+
endSeconds: segment.endTimeSeconds,
|
|
160
|
+
turnIndex: segment.dialogueInputIndex,
|
|
161
|
+
voice: segment.voiceId,
|
|
162
|
+
...alignment ? { text: alignment.characters.slice(segment.characterStartIndex, segment.characterEndIndex).join("") } : {}
|
|
163
|
+
}));
|
|
164
|
+
return fields;
|
|
165
|
+
}
|
|
74
166
|
function mapVoiceSettings(settings, speedOverride) {
|
|
75
167
|
return {
|
|
76
168
|
...settings.stability != null ? { stability: settings.stability } : {},
|
|
@@ -81,6 +173,35 @@ function mapVoiceSettings(settings, speedOverride) {
|
|
|
81
173
|
};
|
|
82
174
|
}
|
|
83
175
|
/**
|
|
176
|
+
* ElevenLabs voice categories map onto {@link VoiceOrigin} with two joins:
|
|
177
|
+
* `famous` and `high_quality` are both curated tiers, so both read as
|
|
178
|
+
* `'professional'`. An unrecognized category is dropped rather than guessed,
|
|
179
|
+
* which keeps an `origins` filter from silently matching the wrong thing.
|
|
180
|
+
*/
|
|
181
|
+
function toVoiceOrigin(category) {
|
|
182
|
+
switch (category) {
|
|
183
|
+
case "premade": return "premade";
|
|
184
|
+
case "generated": return "generated";
|
|
185
|
+
case "cloned": return "cloned";
|
|
186
|
+
case "professional":
|
|
187
|
+
case "famous":
|
|
188
|
+
case "high_quality": return "professional";
|
|
189
|
+
case void 0:
|
|
190
|
+
default: return;
|
|
191
|
+
}
|
|
192
|
+
}
|
|
193
|
+
function toCatalogVoice(voice) {
|
|
194
|
+
const origin = toVoiceOrigin(voice.category);
|
|
195
|
+
return {
|
|
196
|
+
voiceId: voice.voiceId,
|
|
197
|
+
...voice.name ? { name: voice.name } : {},
|
|
198
|
+
...origin ? { origin } : {},
|
|
199
|
+
...voice.description ? { description: voice.description } : {},
|
|
200
|
+
...voice.previewUrl ? { previewUrl: voice.previewUrl } : {},
|
|
201
|
+
...voice.labels ? { labels: voice.labels } : {}
|
|
202
|
+
};
|
|
203
|
+
}
|
|
204
|
+
/**
|
|
84
205
|
* Map the standard TTSOptions `format` (mp3/opus/aac/flac/wav/pcm) to a
|
|
85
206
|
* reasonable ElevenLabs `outputFormat` so callers don't need to know the
|
|
86
207
|
* full codec/samplerate string for the common case.
|
|
@@ -88,12 +209,40 @@ function mapVoiceSettings(settings, speedOverride) {
|
|
|
88
209
|
function inferOutputFormatFromResponseFormat(format) {
|
|
89
210
|
switch (format) {
|
|
90
211
|
case "mp3": return "mp3_44100_128";
|
|
91
|
-
case "pcm":
|
|
212
|
+
case "pcm":
|
|
213
|
+
case "wav": return "pcm_44100";
|
|
92
214
|
case "opus": return "opus_48000_128";
|
|
93
215
|
case void 0: return;
|
|
94
|
-
default:
|
|
216
|
+
default: throw new Error(`ElevenLabs TTS does not support format '${format}'. Use mp3, pcm, opus, or wav.`);
|
|
95
217
|
}
|
|
96
218
|
}
|
|
219
|
+
function base64ToArrayBuffer(base64) {
|
|
220
|
+
const binary = atob(base64);
|
|
221
|
+
const bytes = new Uint8Array(binary.length);
|
|
222
|
+
for (let i = 0; i < binary.length; i += 1) bytes[i] = binary.charCodeAt(i);
|
|
223
|
+
return bytes.buffer;
|
|
224
|
+
}
|
|
225
|
+
/** Wrap ElevenLabs pcm_44100 (16-bit little-endian mono) in a RIFF/WAV container. */
|
|
226
|
+
function wrapPcmAsWav(pcm) {
|
|
227
|
+
const wav = new ArrayBuffer(44 + pcm.byteLength);
|
|
228
|
+
const bytes = new Uint8Array(wav);
|
|
229
|
+
const header = new DataView(wav);
|
|
230
|
+
const encoder = new TextEncoder();
|
|
231
|
+
bytes.set(encoder.encode("RIFF"), 0);
|
|
232
|
+
header.setUint32(4, 36 + pcm.byteLength, true);
|
|
233
|
+
bytes.set(encoder.encode("WAVEfmt "), 8);
|
|
234
|
+
header.setUint32(16, 16, true);
|
|
235
|
+
header.setUint16(20, 1, true);
|
|
236
|
+
header.setUint16(22, 1, true);
|
|
237
|
+
header.setUint32(24, 44100, true);
|
|
238
|
+
header.setUint32(28, 88200, true);
|
|
239
|
+
header.setUint16(32, 2, true);
|
|
240
|
+
header.setUint16(34, 16, true);
|
|
241
|
+
bytes.set(encoder.encode("data"), 36);
|
|
242
|
+
header.setUint32(40, pcm.byteLength, true);
|
|
243
|
+
bytes.set(new Uint8Array(pcm), 44);
|
|
244
|
+
return wav;
|
|
245
|
+
}
|
|
97
246
|
/**
|
|
98
247
|
* Create an ElevenLabs speech adapter using `ELEVENLABS_API_KEY` from env.
|
|
99
248
|
*/
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"speech.js","names":[],"sources":["../../../src/adapters/speech.ts"],"sourcesContent":["import { BaseTTSAdapter } from '@tanstack/ai/adapters'\nimport {\n arrayBufferToBase64,\n createElevenLabsClient,\n generateId,\n parseOutputFormat,\n readStreamToArrayBuffer,\n} from '../utils/client'\nimport type { ElevenLabsClient } from '@elevenlabs/elevenlabs-js'\nimport type { TTSOptions, TTSResult } from '@tanstack/ai'\nimport type { ElevenLabsClientConfig } from '../utils/client'\nimport type { ElevenLabsOutputFormat, ElevenLabsTTSModel } from '../model-meta'\n\n/**\n * ElevenLabs voice settings overrides. All fields are optional — omitted\n * values fall back to the voice's stored defaults.\n * @see https://elevenlabs.io/docs/api-reference/text-to-speech/convert\n */\nexport interface ElevenLabsVoiceSettings {\n /** Voice stability, 0..1. Default 0.5. */\n stability?: number\n /** Similarity boost, 0..1. Default 0.75. */\n similarityBoost?: number\n /** Style exaggeration, 0..1. Default 0. */\n style?: number\n /** Playback speed. Default 1.0. */\n speed?: number\n /** Clarity/presence boost. Default true. */\n useSpeakerBoost?: boolean\n}\n\n/**\n * Provider-specific TTS options. `voice` on `generateSpeech()` takes priority\n * over `voiceId` here, but we expose the same field for callers that prefer\n * to keep voice configuration inside the adapter config.\n */\nexport interface ElevenLabsSpeechProviderOptions {\n /** ElevenLabs voice ID to synthesize. Required if `generateSpeech().voice` is not set. */\n voiceId?: string\n /** Output audio format encoded as `codec_samplerate[_bitrate]`. Defaults to `mp3_44100_128`. */\n outputFormat?: ElevenLabsOutputFormat\n /** Voice-settings overrides for this request only. */\n voiceSettings?: ElevenLabsVoiceSettings\n /** ISO-639-1 language code to enforce (e.g. `'en'`, `'ja'`). */\n languageCode?: string\n /** Deterministic sampling seed, 0..4294967295. */\n seed?: number\n /** Previous text for stitching adjacent clips. */\n previousText?: string\n /** Next text for stitching adjacent clips. */\n nextText?: string\n /** Previous request IDs for stitching (max 3). */\n previousRequestIds?: Array<string>\n /** Next request IDs for stitching (max 3). */\n nextRequestIds?: Array<string>\n /** Text normalization toggle. Default `'auto'`. */\n applyTextNormalization?: 'auto' | 'on' | 'off'\n /** Language-specific text normalization (currently Japanese only, adds latency). */\n applyLanguageTextNormalization?: boolean\n /** Latency optimization level, 0..4. */\n optimizeStreamingLatency?: number\n /** Enable logging. Set false for zero-retention mode (enterprise only). */\n enableLogging?: boolean\n}\n\n/**\n * ElevenLabs text-to-speech adapter built on the official\n * `@elevenlabs/elevenlabs-js` SDK.\n *\n * @example\n * ```ts\n * const adapter = elevenlabsSpeech('eleven_multilingual_v2')\n * const result = await generateSpeech({\n * adapter,\n * text: 'Hello, world!',\n * voice: '21m00Tcm4TlvDq8ikWAM',\n * })\n * ```\n */\nexport class ElevenLabsSpeechAdapter<\n TModel extends ElevenLabsTTSModel,\n> extends BaseTTSAdapter<TModel, ElevenLabsSpeechProviderOptions> {\n readonly name = 'elevenlabs' as const\n\n private readonly client: ElevenLabsClient\n\n constructor(model: TModel, config?: ElevenLabsClientConfig) {\n super(model, config ?? {})\n this.client = createElevenLabsClient(config)\n }\n\n async generateSpeech(\n options: TTSOptions<ElevenLabsSpeechProviderOptions>,\n ): Promise<TTSResult> {\n const { logger } = options\n logger.request(\n `activity=generateSpeech provider=elevenlabs model=${this.model}`,\n { provider: 'elevenlabs', model: this.model },\n )\n try {\n const voiceId = options.voice ?? options.modelOptions?.voiceId\n if (!voiceId) {\n throw new Error(\n 'ElevenLabs TTS requires a voice. Pass `voice` on generateSpeech() or `voiceId` in modelOptions.',\n )\n }\n const {\n outputFormat,\n voiceSettings,\n languageCode,\n seed,\n previousText,\n nextText,\n previousRequestIds,\n nextRequestIds,\n applyTextNormalization,\n applyLanguageTextNormalization,\n optimizeStreamingLatency,\n enableLogging,\n } = options.modelOptions ?? {}\n const effectiveOutputFormat =\n outputFormat ?? inferOutputFormatFromResponseFormat(options.format)\n\n const stream = await this.client.textToSpeech.convert(voiceId, {\n text: options.text,\n modelId: this.model,\n ...(effectiveOutputFormat\n ? { outputFormat: effectiveOutputFormat }\n : {}),\n ...(voiceSettings\n ? { voiceSettings: mapVoiceSettings(voiceSettings, options.speed) }\n : options.speed != null\n ? { voiceSettings: { speed: options.speed } }\n : {}),\n ...(languageCode ? { languageCode } : {}),\n ...(seed != null ? { seed } : {}),\n ...(previousText ? { previousText } : {}),\n ...(nextText ? { nextText } : {}),\n ...(previousRequestIds ? { previousRequestIds } : {}),\n ...(nextRequestIds ? { nextRequestIds } : {}),\n ...(applyTextNormalization ? { applyTextNormalization } : {}),\n ...(applyLanguageTextNormalization != null\n ? { applyLanguageTextNormalization }\n : {}),\n ...(optimizeStreamingLatency != null\n ? { optimizeStreamingLatency }\n : {}),\n ...(enableLogging != null ? { enableLogging } : {}),\n })\n\n const buffer = await readStreamToArrayBuffer(stream)\n const base64 = arrayBufferToBase64(buffer)\n const { format, contentType } = parseOutputFormat(effectiveOutputFormat)\n\n return {\n id: generateId(this.name),\n model: this.model,\n audio: base64,\n format,\n contentType,\n }\n } catch (error) {\n logger.errors('elevenlabs.generateSpeech fatal', {\n error,\n source: 'elevenlabs.generateSpeech',\n })\n throw error\n }\n }\n\n protected override generateId(): string {\n return generateId(this.name)\n }\n}\n\nfunction mapVoiceSettings(\n settings: ElevenLabsVoiceSettings,\n speedOverride: number | undefined,\n): Record<string, unknown> {\n return {\n ...(settings.stability != null ? { stability: settings.stability } : {}),\n ...(settings.similarityBoost != null\n ? { similarityBoost: settings.similarityBoost }\n : {}),\n ...(settings.style != null ? { style: settings.style } : {}),\n ...(speedOverride != null\n ? { speed: speedOverride }\n : settings.speed != null\n ? { speed: settings.speed }\n : {}),\n ...(settings.useSpeakerBoost != null\n ? { useSpeakerBoost: settings.useSpeakerBoost }\n : {}),\n }\n}\n\n/**\n * Map the standard TTSOptions `format` (mp3/opus/aac/flac/wav/pcm) to a\n * reasonable ElevenLabs `outputFormat` so callers don't need to know the\n * full codec/samplerate string for the common case.\n */\nfunction inferOutputFormatFromResponseFormat(\n format: TTSOptions['format'] | undefined,\n): ElevenLabsOutputFormat | undefined {\n switch (format) {\n case 'mp3':\n return 'mp3_44100_128'\n case 'pcm':\n return 'pcm_44100'\n case 'opus':\n return 'opus_48000_128'\n case undefined:\n return undefined\n case 'aac':\n case 'flac':\n case 'wav':\n default:\n // `aac` / `flac` / `wav` are not native ElevenLabs formats —\n // fall back to mp3 rather than blowing up mid-request.\n return 'mp3_44100_128'\n }\n}\n\n/**\n * Create an ElevenLabs speech adapter using `ELEVENLABS_API_KEY` from env.\n */\nexport function elevenlabsSpeech<TModel extends ElevenLabsTTSModel>(\n model: TModel,\n config?: ElevenLabsClientConfig,\n): ElevenLabsSpeechAdapter<TModel> {\n return new ElevenLabsSpeechAdapter(model, config)\n}\n\n/**\n * Create an ElevenLabs speech adapter with an explicit API key.\n */\nexport function createElevenLabsSpeech<TModel extends ElevenLabsTTSModel>(\n model: TModel,\n apiKey: string,\n config?: Omit<ElevenLabsClientConfig, 'apiKey'>,\n): ElevenLabsSpeechAdapter<TModel> {\n return new ElevenLabsSpeechAdapter(model, { apiKey, ...config })\n}\n"],"mappings":"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"}
|
|
1
|
+
{"version":3,"file":"speech.js","names":[],"sources":["../../../src/adapters/speech.ts"],"sourcesContent":["import { BaseTTSAdapter } from '@tanstack/ai/adapters'\nimport {\n arrayBufferToBase64,\n createElevenLabsClient,\n generateId,\n parseOutputFormat,\n readStreamToArrayBuffer,\n} from '../utils/client'\nimport type { ElevenLabs, ElevenLabsClient } from '@elevenlabs/elevenlabs-js'\nimport type {\n CatalogVoice,\n ListVoicesOptions,\n ListVoicesResult,\n TTSAlignment,\n TTSCapabilities,\n TTSOptions,\n TTSResult,\n TTSSegment,\n VoiceOrigin,\n} from '@tanstack/ai'\nimport type { ElevenLabsClientConfig } from '../utils/client'\nimport type { ElevenLabsOutputFormat, ElevenLabsTTSModel } from '../model-meta'\n\n/**\n * ElevenLabs voice settings overrides. All fields are optional — omitted\n * values fall back to the voice's stored defaults.\n * @see https://elevenlabs.io/docs/api-reference/text-to-speech/convert\n */\nexport interface ElevenLabsVoiceSettings {\n /** Voice stability, 0..1. Default 0.5. */\n stability?: number\n /** Similarity boost, 0..1. Default 0.75. */\n similarityBoost?: number\n /** Style exaggeration, 0..1. Default 0. */\n style?: number\n /** Playback speed. Default 1.0. */\n speed?: number\n /** Clarity/presence boost. Default true. */\n useSpeakerBoost?: boolean\n}\n\n/**\n * Provider-specific TTS options. `voice` on `generateSpeech()` takes priority\n * over `voiceId` here, but we expose the same field for callers that prefer\n * to keep voice configuration inside the adapter config.\n */\nexport interface ElevenLabsSpeechProviderOptions {\n /** ElevenLabs voice ID to synthesize. Required if `generateSpeech().voice` is not set. */\n voiceId?: string\n /** Output audio format encoded as `codec_samplerate[_bitrate]`. Overrides `format`, including WAV wrapping. Defaults to `mp3_44100_128`. */\n outputFormat?: ElevenLabsOutputFormat\n /** Voice-settings overrides for this request only. */\n voiceSettings?: ElevenLabsVoiceSettings\n /** ISO-639-1 language code to enforce (e.g. `'en'`, `'ja'`). */\n languageCode?: string\n /** Deterministic sampling seed, 0..4294967295. */\n seed?: number\n /** Previous text for stitching adjacent clips. */\n previousText?: string\n /** Next text for stitching adjacent clips. */\n nextText?: string\n /** Previous request IDs for stitching (max 3). */\n previousRequestIds?: Array<string>\n /** Next request IDs for stitching (max 3). */\n nextRequestIds?: Array<string>\n /** Text normalization toggle. Default `'auto'`. */\n applyTextNormalization?: 'auto' | 'on' | 'off'\n /** Language-specific text normalization (currently Japanese only, adds latency). */\n applyLanguageTextNormalization?: boolean\n /** Latency optimization level, 0..4. */\n optimizeStreamingLatency?: number\n /** Enable logging. Set false for zero-retention mode (enterprise only). */\n enableLogging?: boolean\n}\n\n/**\n * ElevenLabs text-to-speech adapter built on the official\n * `@elevenlabs/elevenlabs-js` SDK.\n *\n * @example\n * ```ts\n * const adapter = elevenlabsSpeech('eleven_multilingual_v2')\n * const result = await generateSpeech({\n * adapter,\n * text: 'Hello, world!',\n * voice: '21m00Tcm4TlvDq8ikWAM',\n * })\n * ```\n */\nexport class ElevenLabsSpeechAdapter<\n TModel extends ElevenLabsTTSModel,\n> extends BaseTTSAdapter<TModel, ElevenLabsSpeechProviderOptions> {\n readonly name = 'elevenlabs' as const\n\n /**\n * `textToDialogue` accepts up to 10 distinct voice ids, and both endpoints\n * have a `…WithTimestamps` twin that returns character alignment.\n */\n override readonly capabilities: TTSCapabilities = {\n maxSpeakers: 10,\n timestamps: true,\n }\n\n private readonly client: ElevenLabsClient\n\n constructor(model: TModel, config?: ElevenLabsClientConfig) {\n super(model, config ?? {})\n this.client = createElevenLabsClient(config)\n }\n\n async generateSpeech(\n options: TTSOptions<ElevenLabsSpeechProviderOptions>,\n ): Promise<TTSResult> {\n const { logger } = options\n logger.request(\n `activity=generateSpeech provider=elevenlabs model=${this.model}`,\n { provider: 'elevenlabs', model: this.model },\n )\n try {\n const {\n outputFormat,\n voiceSettings,\n languageCode,\n seed,\n previousText,\n nextText,\n previousRequestIds,\n nextRequestIds,\n applyTextNormalization,\n applyLanguageTextNormalization,\n optimizeStreamingLatency,\n enableLogging,\n } = options.modelOptions ?? {}\n const effectiveOutputFormat =\n outputFormat ?? inferOutputFormatFromResponseFormat(options.format)\n const wrapAsWav = outputFormat == null && options.format === 'wav'\n const { format, contentType } = wrapAsWav\n ? { format: 'wav', contentType: 'audio/wav' }\n : parseOutputFormat(effectiveOutputFormat)\n // `format: 'wav'` asks ElevenLabs for pcm_44100 and wraps it here, on\n // every endpoint: the byte-stream pair hands back raw bytes, the\n // timestamped pair hands back the same bytes as base64.\n const encodeAudio = (buffer: ArrayBuffer) =>\n arrayBufferToBase64(wrapAsWav ? wrapPcmAsWav(buffer) : buffer)\n const encodeAudioBase64 = (audioBase64: string) =>\n wrapAsWav ? encodeAudio(base64ToArrayBuffer(audioBase64)) : audioBase64\n const base = {\n modelId: this.model,\n ...(effectiveOutputFormat\n ? { outputFormat: effectiveOutputFormat }\n : {}),\n ...(languageCode ? { languageCode } : {}),\n ...(seed != null ? { seed } : {}),\n ...(applyTextNormalization ? { applyTextNormalization } : {}),\n }\n\n // Four endpoints, picked by (turns?, timestamps?). The dialogue pair\n // takes `inputs` instead of a voice id in the path, and the timestamped\n // pair returns JSON (base64 audio + alignment) instead of a byte stream.\n if (options.turns) {\n const inputs = options.turns.map((turn) => ({\n text: turn.text,\n voiceId: turn.voice,\n }))\n const dialogue = {\n ...base,\n inputs,\n ...(voiceSettings?.stability != null\n ? { settings: { stability: voiceSettings.stability } }\n : {}),\n }\n\n if (options.timestamps) {\n const response =\n await this.client.textToDialogue.convertWithTimestamps(dialogue)\n return {\n id: generateId(this.name),\n model: this.model,\n audio: encodeAudioBase64(response.audioBase64),\n format,\n contentType,\n ...toAlignmentFields(response.alignment, response.voiceSegments),\n }\n }\n\n const stream = await this.client.textToDialogue.convert(dialogue)\n return {\n id: generateId(this.name),\n model: this.model,\n audio: encodeAudio(await readStreamToArrayBuffer(stream)),\n format,\n contentType,\n }\n }\n\n const voiceId = options.voice ?? options.modelOptions?.voiceId\n if (!voiceId) {\n throw new Error(\n 'ElevenLabs TTS requires a voice. Pass `voice` on generateSpeech() or `voiceId` in modelOptions.',\n )\n }\n const single = {\n ...base,\n text: options.text,\n ...(voiceSettings\n ? { voiceSettings: mapVoiceSettings(voiceSettings, options.speed) }\n : options.speed != null\n ? { voiceSettings: { speed: options.speed } }\n : {}),\n ...(previousText ? { previousText } : {}),\n ...(nextText ? { nextText } : {}),\n ...(previousRequestIds ? { previousRequestIds } : {}),\n ...(nextRequestIds ? { nextRequestIds } : {}),\n ...(applyLanguageTextNormalization != null\n ? { applyLanguageTextNormalization }\n : {}),\n ...(enableLogging != null ? { enableLogging } : {}),\n }\n\n if (options.timestamps) {\n const response = await this.client.textToSpeech.convertWithTimestamps(\n voiceId,\n single,\n )\n return {\n id: generateId(this.name),\n model: this.model,\n audio: encodeAudioBase64(response.audioBase64),\n format,\n contentType,\n ...toAlignmentFields(response.alignment, undefined),\n }\n }\n\n const stream = await this.client.textToSpeech.convert(voiceId, {\n ...single,\n ...(optimizeStreamingLatency != null\n ? { optimizeStreamingLatency }\n : {}),\n })\n\n return {\n id: generateId(this.name),\n model: this.model,\n audio: encodeAudio(await readStreamToArrayBuffer(stream)),\n format,\n contentType,\n }\n } catch (error) {\n logger.errors('elevenlabs.generateSpeech fatal', {\n error,\n source: 'elevenlabs.generateSpeech',\n })\n throw error\n }\n }\n\n /**\n * List the voices this API key can use, via `GET /v1/voices`.\n *\n * The catalog is per-account and grows every time `generateVoice()` saves a\n * voice, so it has to be read at runtime rather than shipped as a const.\n */\n override async listVoices(\n options?: ListVoicesOptions,\n ): Promise<ListVoicesResult> {\n const response = await this.client.voices.getAll(\n {},\n options?.abortSignal ? { abortSignal: options.abortSignal } : {},\n )\n\n const voices = response.voices.map(toCatalogVoice)\n const origins = options?.origins\n // ElevenLabs has no origin filter on /v1/voices, so narrow in memory.\n return {\n voices: origins\n ? voices.filter(\n (voice) => voice.origin && origins.includes(voice.origin),\n )\n : voices,\n }\n }\n\n protected override generateId(): string {\n return generateId(this.name)\n }\n}\n\n/**\n * Map the ElevenLabs timestamp payload onto the core `alignment` / `segments`\n * fields. `voiceSegments` only comes back from the dialogue endpoint; its\n * character indices slice the alignment into per-turn text.\n */\nfunction toAlignmentFields(\n alignment: ElevenLabs.CharacterAlignmentResponseModel | undefined,\n voiceSegments: Array<ElevenLabs.VoiceSegment> | undefined,\n): { alignment?: TTSAlignment; segments?: Array<TTSSegment> } {\n const fields: { alignment?: TTSAlignment; segments?: Array<TTSSegment> } = {}\n if (alignment) {\n fields.alignment = {\n unit: 'character',\n texts: alignment.characters,\n startSeconds: alignment.characterStartTimesSeconds,\n endSeconds: alignment.characterEndTimesSeconds,\n }\n }\n if (voiceSegments && voiceSegments.length > 0) {\n fields.segments = voiceSegments.map((segment) => ({\n startSeconds: segment.startTimeSeconds,\n endSeconds: segment.endTimeSeconds,\n turnIndex: segment.dialogueInputIndex,\n voice: segment.voiceId,\n ...(alignment\n ? {\n text: alignment.characters\n .slice(segment.characterStartIndex, segment.characterEndIndex)\n .join(''),\n }\n : {}),\n }))\n }\n return fields\n}\n\nfunction mapVoiceSettings(\n settings: ElevenLabsVoiceSettings,\n speedOverride: number | undefined,\n): Record<string, unknown> {\n return {\n ...(settings.stability != null ? { stability: settings.stability } : {}),\n ...(settings.similarityBoost != null\n ? { similarityBoost: settings.similarityBoost }\n : {}),\n ...(settings.style != null ? { style: settings.style } : {}),\n ...(speedOverride != null\n ? { speed: speedOverride }\n : settings.speed != null\n ? { speed: settings.speed }\n : {}),\n ...(settings.useSpeakerBoost != null\n ? { useSpeakerBoost: settings.useSpeakerBoost }\n : {}),\n }\n}\n\n/**\n * ElevenLabs voice categories map onto {@link VoiceOrigin} with two joins:\n * `famous` and `high_quality` are both curated tiers, so both read as\n * `'professional'`. An unrecognized category is dropped rather than guessed,\n * which keeps an `origins` filter from silently matching the wrong thing.\n */\nfunction toVoiceOrigin(\n category: ElevenLabs.VoiceCategory | undefined,\n): VoiceOrigin | undefined {\n switch (category) {\n case 'premade':\n return 'premade'\n case 'generated':\n return 'generated'\n case 'cloned':\n return 'cloned'\n case 'professional':\n case 'famous':\n case 'high_quality':\n return 'professional'\n case undefined:\n default:\n return undefined\n }\n}\n\nfunction toCatalogVoice(voice: ElevenLabs.Voice): CatalogVoice {\n const origin = toVoiceOrigin(voice.category)\n return {\n voiceId: voice.voiceId,\n ...(voice.name ? { name: voice.name } : {}),\n ...(origin ? { origin } : {}),\n ...(voice.description ? { description: voice.description } : {}),\n ...(voice.previewUrl ? { previewUrl: voice.previewUrl } : {}),\n ...(voice.labels ? { labels: voice.labels } : {}),\n }\n}\n\n/**\n * Map the standard TTSOptions `format` (mp3/opus/aac/flac/wav/pcm) to a\n * reasonable ElevenLabs `outputFormat` so callers don't need to know the\n * full codec/samplerate string for the common case.\n */\nfunction inferOutputFormatFromResponseFormat(\n format: TTSOptions['format'] | undefined,\n): ElevenLabsOutputFormat | undefined {\n switch (format) {\n case 'mp3':\n return 'mp3_44100_128'\n case 'pcm':\n case 'wav':\n return 'pcm_44100'\n case 'opus':\n return 'opus_48000_128'\n case undefined:\n return undefined\n case 'aac':\n case 'flac':\n default:\n throw new Error(\n `ElevenLabs TTS does not support format '${format}'. Use mp3, pcm, opus, or wav.`,\n )\n }\n}\n\nfunction base64ToArrayBuffer(base64: string): ArrayBuffer {\n const binary = atob(base64)\n const bytes = new Uint8Array(binary.length)\n for (let i = 0; i < binary.length; i += 1) bytes[i] = binary.charCodeAt(i)\n return bytes.buffer\n}\n\n/** Wrap ElevenLabs pcm_44100 (16-bit little-endian mono) in a RIFF/WAV container. */\nfunction wrapPcmAsWav(pcm: ArrayBuffer): ArrayBuffer {\n const wav = new ArrayBuffer(44 + pcm.byteLength)\n const bytes = new Uint8Array(wav)\n const header = new DataView(wav)\n const encoder = new TextEncoder()\n bytes.set(encoder.encode('RIFF'), 0)\n header.setUint32(4, 36 + pcm.byteLength, true)\n bytes.set(encoder.encode('WAVEfmt '), 8)\n header.setUint32(16, 16, true) // PCM format chunk size\n header.setUint16(20, 1, true) // PCM encoding\n header.setUint16(22, 1, true) // mono\n header.setUint32(24, 44100, true) // sample rate\n header.setUint32(28, 44100 * 2, true) // bytes per second\n header.setUint16(32, 2, true) // bytes per sample\n header.setUint16(34, 16, true) // bits per sample\n bytes.set(encoder.encode('data'), 36)\n header.setUint32(40, pcm.byteLength, true)\n bytes.set(new Uint8Array(pcm), 44)\n return wav\n}\n\n/**\n * Create an ElevenLabs speech adapter using `ELEVENLABS_API_KEY` from env.\n */\nexport function elevenlabsSpeech<TModel extends ElevenLabsTTSModel>(\n model: TModel,\n config?: ElevenLabsClientConfig,\n): ElevenLabsSpeechAdapter<TModel> {\n return new ElevenLabsSpeechAdapter(model, config)\n}\n\n/**\n * Create an ElevenLabs speech adapter with an explicit API key.\n */\nexport function createElevenLabsSpeech<TModel extends ElevenLabsTTSModel>(\n model: TModel,\n apiKey: string,\n config?: Omit<ElevenLabsClientConfig, 'apiKey'>,\n): ElevenLabsSpeechAdapter<TModel> {\n return new ElevenLabsSpeechAdapter(model, { apiKey, ...config })\n}\n"],"mappings":"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"}
|
|
@@ -0,0 +1,85 @@
|
|
|
1
|
+
import { BaseVoiceAdapter } from '@tanstack/ai/adapters';
|
|
2
|
+
import { VoiceGenerationOptions, VoiceResult } from '@tanstack/ai';
|
|
3
|
+
import { ElevenLabsClientConfig } from '../utils/client.js';
|
|
4
|
+
import { ElevenLabsOutputFormat, ElevenLabsVoiceModel } from '../model-meta.js';
|
|
5
|
+
/**
|
|
6
|
+
* Provider-specific voice-design options. Fields map 1:1 onto the SDK's
|
|
7
|
+
* `VoiceDesignRequestModel` — mirroring the names so ElevenLabs'
|
|
8
|
+
* documentation stays useful here.
|
|
9
|
+
* @see https://elevenlabs.io/docs/api-reference/text-to-voice/design
|
|
10
|
+
*/
|
|
11
|
+
export interface ElevenLabsVoiceProviderOptions {
|
|
12
|
+
/** Output audio format for the previews, `codec_samplerate[_bitrate]`. */
|
|
13
|
+
outputFormat?: ElevenLabsOutputFormat;
|
|
14
|
+
/** Line the previews speak, 100..1000 characters. */
|
|
15
|
+
text?: string;
|
|
16
|
+
/** Let ElevenLabs write the preview line from the description. */
|
|
17
|
+
autoGenerateText?: boolean;
|
|
18
|
+
/** Preview loudness, -1 (quietest) to 1 (loudest). 0 is roughly -24 LUFS. */
|
|
19
|
+
loudness?: number;
|
|
20
|
+
/** Deterministic sampling seed — same seed and inputs produce the same voice. */
|
|
21
|
+
seed?: number;
|
|
22
|
+
/** How closely to follow the description. High values can sound robotic. */
|
|
23
|
+
guidanceScale?: number;
|
|
24
|
+
/** Higher quality trades variety for fidelity. */
|
|
25
|
+
quality?: number;
|
|
26
|
+
/** Let ElevenLabs expand a short description into a detailed one. */
|
|
27
|
+
shouldEnhance?: boolean;
|
|
28
|
+
/**
|
|
29
|
+
* Balance of description against reference audio, 0 (almost all reference)
|
|
30
|
+
* to 1 (almost all description). `eleven_ttv_v3` only.
|
|
31
|
+
*/
|
|
32
|
+
promptStrength?: number;
|
|
33
|
+
/** Metadata stored on the voice. Only used when the voice is saved. */
|
|
34
|
+
labels?: Record<string, string>;
|
|
35
|
+
/** Remixing session to attach these generations to. */
|
|
36
|
+
remixingSessionId?: string;
|
|
37
|
+
/** Remixing session iteration to attach these generations to. */
|
|
38
|
+
remixingSessionIterationId?: string;
|
|
39
|
+
}
|
|
40
|
+
/**
|
|
41
|
+
* ElevenLabs voice-design adapter built on the official
|
|
42
|
+
* `@elevenlabs/elevenlabs-js` SDK.
|
|
43
|
+
*
|
|
44
|
+
* ElevenLabs designs voices in two steps — generate previews, then promote one
|
|
45
|
+
* into a real voice — but that is an implementation detail. Pass `name` to get
|
|
46
|
+
* a saved voice back; leave it off to audition previews first.
|
|
47
|
+
*
|
|
48
|
+
* @example Audition previews
|
|
49
|
+
* ```ts
|
|
50
|
+
* const result = await generateVoice({
|
|
51
|
+
* adapter: elevenlabsVoiceDesign('eleven_ttv_v3'),
|
|
52
|
+
* prompt: 'A warm, gravelly narrator in his sixties with a slight Irish lilt',
|
|
53
|
+
* })
|
|
54
|
+
* ```
|
|
55
|
+
*
|
|
56
|
+
* @example Save the voice
|
|
57
|
+
* ```ts
|
|
58
|
+
* const result = await generateVoice({
|
|
59
|
+
* adapter: elevenlabsVoiceDesign('eleven_ttv_v3'),
|
|
60
|
+
* prompt: 'A bright, upbeat product demo host',
|
|
61
|
+
* name: 'Demo Host',
|
|
62
|
+
* })
|
|
63
|
+
* ```
|
|
64
|
+
*/
|
|
65
|
+
export declare class ElevenLabsVoiceAdapter<TModel extends ElevenLabsVoiceModel> extends BaseVoiceAdapter<TModel, ElevenLabsVoiceProviderOptions> {
|
|
66
|
+
readonly name: "elevenlabs";
|
|
67
|
+
private readonly client;
|
|
68
|
+
constructor(model: TModel, config?: ElevenLabsClientConfig);
|
|
69
|
+
generateVoice(options: VoiceGenerationOptions<ElevenLabsVoiceProviderOptions>): Promise<VoiceResult>;
|
|
70
|
+
/**
|
|
71
|
+
* Promote the best preview into a real library voice. The remaining
|
|
72
|
+
* generated ids go along as `playedNotSelectedVoiceIds` — ElevenLabs uses
|
|
73
|
+
* them as RLHF signal for future designs.
|
|
74
|
+
*/
|
|
75
|
+
private promoteFirstPreview;
|
|
76
|
+
protected generateId(): string;
|
|
77
|
+
}
|
|
78
|
+
/**
|
|
79
|
+
* Create an ElevenLabs voice-design adapter using `ELEVENLABS_API_KEY` from env.
|
|
80
|
+
*/
|
|
81
|
+
export declare function elevenlabsVoiceDesign<TModel extends ElevenLabsVoiceModel>(model: TModel, config?: ElevenLabsClientConfig): ElevenLabsVoiceAdapter<TModel>;
|
|
82
|
+
/**
|
|
83
|
+
* Create an ElevenLabs voice-design adapter with an explicit API key.
|
|
84
|
+
*/
|
|
85
|
+
export declare function createElevenLabsVoiceDesign<TModel extends ElevenLabsVoiceModel>(model: TModel, apiKey: string, config?: Omit<ElevenLabsClientConfig, 'apiKey'>): ElevenLabsVoiceAdapter<TModel>;
|