@tanstack/ai-elevenlabs 0.1.8 → 0.2.1

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.
Files changed (37) hide show
  1. package/dist/esm/adapters/audio.d.ts +93 -0
  2. package/dist/esm/adapters/audio.js +106 -0
  3. package/dist/esm/adapters/audio.js.map +1 -0
  4. package/dist/esm/adapters/speech.d.ts +83 -0
  5. package/dist/esm/adapters/speech.js +109 -0
  6. package/dist/esm/adapters/speech.js.map +1 -0
  7. package/dist/esm/adapters/transcription.d.ts +79 -0
  8. package/dist/esm/adapters/transcription.js +142 -0
  9. package/dist/esm/adapters/transcription.js.map +1 -0
  10. package/dist/esm/index.d.ts +5 -0
  11. package/dist/esm/index.js +21 -1
  12. package/dist/esm/index.js.map +1 -1
  13. package/dist/esm/model-meta.d.ts +42 -0
  14. package/dist/esm/model-meta.js +32 -0
  15. package/dist/esm/model-meta.js.map +1 -0
  16. package/dist/esm/realtime/adapter.d.ts +1 -1
  17. package/dist/esm/realtime/adapter.js +1 -1
  18. package/dist/esm/realtime/adapter.js.map +1 -1
  19. package/dist/esm/realtime/token.d.ts +11 -7
  20. package/dist/esm/realtime/token.js +8 -32
  21. package/dist/esm/realtime/token.js.map +1 -1
  22. package/dist/esm/realtime/types.d.ts +5 -2
  23. package/dist/esm/utils/client.d.ts +62 -0
  24. package/dist/esm/utils/client.js +124 -0
  25. package/dist/esm/utils/client.js.map +1 -0
  26. package/dist/esm/utils/index.d.ts +1 -0
  27. package/package.json +15 -7
  28. package/src/adapters/audio.ts +257 -0
  29. package/src/adapters/speech.ts +240 -0
  30. package/src/adapters/transcription.ts +338 -0
  31. package/src/index.ts +64 -0
  32. package/src/model-meta.ts +79 -0
  33. package/src/realtime/adapter.ts +3 -3
  34. package/src/realtime/token.ts +21 -56
  35. package/src/realtime/types.ts +5 -2
  36. package/src/utils/client.ts +196 -0
  37. package/src/utils/index.ts +10 -0
@@ -0,0 +1 @@
1
+ {"version":3,"file":"transcription.js","sources":["../../../src/adapters/transcription.ts"],"sourcesContent":["import { BaseTranscriptionAdapter } from '@tanstack/ai/adapters'\nimport {\n createElevenLabsClient,\n dataUrlToBlob,\n generateId,\n} from '../utils/client'\nimport type { ElevenLabsClient } from '@elevenlabs/elevenlabs-js'\nimport type {\n TranscriptionOptions,\n TranscriptionResult,\n TranscriptionSegment,\n TranscriptionWord,\n} from '@tanstack/ai'\nimport type { ElevenLabsClientConfig } from '../utils/client'\nimport type { ElevenLabsTranscriptionModel } from '../model-meta'\n\n/**\n * Provider-specific options for ElevenLabs Scribe transcription. Fields map\n * 1:1 onto the SDK's `BodySpeechToTextV1SpeechToTextPost` — mirroring the\n * names so documentation stays useful.\n * @see https://elevenlabs.io/docs/api-reference/speech-to-text/convert\n */\nexport interface ElevenLabsTranscriptionProviderOptions {\n /** Annotate non-speech events like (laughter), (footsteps), …. */\n tagAudioEvents?: boolean\n /** Maximum number of speakers in the audio (1..32). */\n numSpeakers?: number\n /** Timestamp granularity for words. */\n timestampsGranularity?: 'word' | 'character' | 'none'\n /** Enable speaker diarization. */\n diarize?: boolean\n /** Diarization threshold (requires `diarize=true` and no `numSpeakers`). */\n diarizationThreshold?: number\n /** Detect speaker roles (agent/customer). Requires diarize=true. */\n detectSpeakerRoles?: boolean\n /** Bias the model towards these keyterms (max 1000). */\n keyterms?: Array<string>\n /**\n * Entity detection: `'all'`, a category (`'pii'`, `'phi'`, `'pci'`,\n * `'other'`, `'offensive_language'`), or a specific entity type.\n */\n entityDetection?: string\n /** Redact entities from the transcript text. Must be a subset of `entityDetection`. */\n entityRedaction?: string\n /** How redacted entities are formatted. */\n entityRedactionMode?: string\n /** Whether to skip filler words / non-speech sounds (scribe_v2 only). */\n noVerbatim?: boolean\n /** Sampling temperature (0..2). */\n temperature?: number\n /** Deterministic sampling seed (0..2147483647). */\n seed?: number\n /** Use `false` for zero-retention mode (enterprise only). */\n enableLogging?: boolean\n /** Multi-channel audio with one speaker per channel. Max 5 channels. */\n useMultiChannel?: boolean\n /**\n * Hint for audio format. Use `'pcm_s16le_16'` to skip encoding for 16-bit\n * PCM @ 16kHz mono little-endian inputs (lower latency).\n */\n fileFormat?: 'pcm_s16le_16' | 'other'\n}\n\n/**\n * ElevenLabs speech-to-text adapter built on the official SDK's Scribe family.\n *\n * @example\n * ```ts\n * const adapter = elevenlabsTranscription('scribe_v1')\n * const result = await generateTranscription({\n * adapter,\n * audio: fileInput,\n * language: 'en',\n * })\n * ```\n */\nexport class ElevenLabsTranscriptionAdapter<\n TModel extends ElevenLabsTranscriptionModel,\n> extends BaseTranscriptionAdapter<\n TModel,\n ElevenLabsTranscriptionProviderOptions\n> {\n readonly name = 'elevenlabs' as const\n\n private client: ElevenLabsClient\n\n constructor(model: TModel, config?: ElevenLabsClientConfig) {\n super(model, config ?? {})\n this.client = createElevenLabsClient(config)\n }\n\n async transcribe(\n options: TranscriptionOptions<ElevenLabsTranscriptionProviderOptions>,\n ): Promise<TranscriptionResult> {\n const { logger } = options\n logger.request(\n `activity=generateTranscription provider=elevenlabs model=${this.model}`,\n { provider: 'elevenlabs', model: this.model },\n )\n try {\n const modelOpts = options.modelOptions ?? {}\n const audioInput = normalizeAudioInput(options.audio)\n\n const response = await this.client.speechToText.convert({\n modelId: this.model,\n ...(audioInput.kind === 'file'\n ? { file: audioInput.value }\n : { cloudStorageUrl: audioInput.value }),\n ...(options.language ? { languageCode: options.language } : {}),\n ...(modelOpts.tagAudioEvents != null\n ? { tagAudioEvents: modelOpts.tagAudioEvents }\n : {}),\n ...(modelOpts.numSpeakers != null\n ? { numSpeakers: modelOpts.numSpeakers }\n : {}),\n ...(modelOpts.timestampsGranularity\n ? { timestampsGranularity: modelOpts.timestampsGranularity }\n : {}),\n ...(modelOpts.diarize != null ? { diarize: modelOpts.diarize } : {}),\n ...(modelOpts.diarizationThreshold != null\n ? { diarizationThreshold: modelOpts.diarizationThreshold }\n : {}),\n ...(modelOpts.detectSpeakerRoles != null\n ? { detectSpeakerRoles: modelOpts.detectSpeakerRoles }\n : {}),\n ...(modelOpts.keyterms ? { keyterms: modelOpts.keyterms } : {}),\n ...(modelOpts.entityDetection\n ? { entityDetection: modelOpts.entityDetection }\n : {}),\n ...(modelOpts.entityRedaction\n ? { entityRedaction: modelOpts.entityRedaction }\n : {}),\n ...(modelOpts.entityRedactionMode\n ? { entityRedactionMode: modelOpts.entityRedactionMode }\n : {}),\n ...(modelOpts.noVerbatim != null\n ? { noVerbatim: modelOpts.noVerbatim }\n : {}),\n ...(modelOpts.temperature != null\n ? { temperature: modelOpts.temperature }\n : {}),\n ...(modelOpts.seed != null ? { seed: modelOpts.seed } : {}),\n ...(modelOpts.enableLogging != null\n ? { enableLogging: modelOpts.enableLogging }\n : {}),\n ...(modelOpts.useMultiChannel != null\n ? { useMultiChannel: modelOpts.useMultiChannel }\n : {}),\n ...(modelOpts.fileFormat ? { fileFormat: modelOpts.fileFormat } : {}),\n } as Parameters<ElevenLabsClient['speechToText']['convert']>[0])\n\n return this.transformResponse(response)\n } catch (error) {\n logger.errors('elevenlabs.generateTranscription fatal', {\n error,\n source: 'elevenlabs.generateTranscription',\n })\n throw error\n }\n }\n\n private transformResponse(\n response: Awaited<ReturnType<ElevenLabsClient['speechToText']['convert']>>,\n ): TranscriptionResult {\n // The SDK types this as a union of single- and multi-channel responses.\n // We treat multi-channel as \"join the channel transcripts\" — consumers\n // who care about per-channel detail can re-parse from `modelOptions`.\n const data = response as unknown as {\n text?: string\n languageCode?: string\n languageProbability?: number\n words?: Array<{\n text: string\n start?: number\n end?: number\n type: string\n speakerId?: string\n }>\n audioDurationSecs?: number\n transcripts?: Array<{\n text?: string\n languageCode?: string\n words?: Array<{\n text: string\n start?: number\n end?: number\n type: string\n speakerId?: string\n }>\n audioDurationSecs?: number\n }>\n }\n\n if (data.transcripts) {\n const joinedText = data.transcripts\n .map((t) => t.text ?? '')\n .filter(Boolean)\n .join('\\n')\n const joinedWords = data.transcripts.flatMap((t) => t.words ?? [])\n const duration = data.transcripts.reduce(\n (max, t) => Math.max(max, t.audioDurationSecs ?? 0),\n 0,\n )\n const firstLang = data.transcripts.find(\n (t) => t.languageCode,\n )?.languageCode\n return {\n id: generateId(this.name),\n model: this.model,\n text: joinedText,\n ...(firstLang ? { language: firstLang } : {}),\n ...(duration ? { duration } : {}),\n ...buildWordsAndSegments(joinedWords),\n }\n }\n\n return {\n id: generateId(this.name),\n model: this.model,\n text: data.text ?? '',\n ...(data.languageCode ? { language: data.languageCode } : {}),\n ...(data.audioDurationSecs ? { duration: data.audioDurationSecs } : {}),\n ...buildWordsAndSegments(data.words ?? []),\n }\n }\n\n protected override generateId(): string {\n return generateId(this.name)\n }\n}\n\ntype NormalizedAudio =\n | { kind: 'file'; value: Blob }\n | { kind: 'url'; value: string }\n\nfunction normalizeAudioInput(\n audio: TranscriptionOptions['audio'],\n): NormalizedAudio {\n if (audio instanceof ArrayBuffer) {\n return { kind: 'file', value: new Blob([audio]) }\n }\n if (typeof audio === 'string') {\n const blob = dataUrlToBlob(audio)\n if (blob) return { kind: 'file', value: blob }\n return { kind: 'url', value: audio }\n }\n // Blob or File both fit the SDK's `Uploadable` contract.\n return { kind: 'file', value: audio }\n}\n\nfunction buildWordsAndSegments(\n words: Array<{\n text: string\n start?: number\n end?: number\n type: string\n speakerId?: string\n }>,\n): {\n words?: Array<TranscriptionWord>\n segments?: Array<TranscriptionSegment>\n} {\n const timedWords = words.filter(\n (w) =>\n typeof w.start === 'number' &&\n typeof w.end === 'number' &&\n w.type !== 'spacing',\n )\n if (timedWords.length === 0) return {}\n\n const outWords: Array<TranscriptionWord> = timedWords.map((w) => ({\n word: w.text,\n start: w.start!,\n end: w.end!,\n }))\n\n // Group contiguous words that share a speaker into segments. If no speaker\n // is ever set, we still emit one segment per sentence-ish grouping.\n const segments: Array<TranscriptionSegment> = []\n let current: {\n start: number\n end: number\n text: string\n speaker?: string\n } | null = null\n\n for (const w of timedWords) {\n if (!current) {\n current = {\n start: w.start!,\n end: w.end!,\n text: w.text,\n ...(w.speakerId ? { speaker: w.speakerId } : {}),\n }\n continue\n }\n if (w.speakerId && current.speaker !== w.speakerId) {\n segments.push({ id: segments.length, ...current })\n current = {\n start: w.start!,\n end: w.end!,\n text: w.text,\n speaker: w.speakerId,\n }\n continue\n }\n current.end = w.end!\n current.text = current.text ? `${current.text} ${w.text}` : w.text\n }\n if (current) segments.push({ id: segments.length, ...current })\n\n return { words: outWords, segments }\n}\n\n/**\n * Create an ElevenLabs transcription adapter using `ELEVENLABS_API_KEY` from env.\n */\nexport function elevenlabsTranscription<\n TModel extends ElevenLabsTranscriptionModel,\n>(\n model: TModel,\n config?: ElevenLabsClientConfig,\n): ElevenLabsTranscriptionAdapter<TModel> {\n return new ElevenLabsTranscriptionAdapter(model, config)\n}\n\n/**\n * Create an ElevenLabs transcription adapter with an explicit API key.\n */\nexport function createElevenLabsTranscription<\n TModel extends ElevenLabsTranscriptionModel,\n>(\n model: TModel,\n apiKey: string,\n config?: Omit<ElevenLabsClientConfig, 'apiKey'>,\n): ElevenLabsTranscriptionAdapter<TModel> {\n return new ElevenLabsTranscriptionAdapter(model, { apiKey, ...config })\n}\n"],"names":[],"mappings":"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"}
@@ -1,2 +1,7 @@
1
1
  export { elevenlabsRealtimeToken, elevenlabsRealtime } from './realtime/index.js';
2
2
  export type { ElevenLabsRealtimeTokenOptions, ElevenLabsRealtimeOptions, ElevenLabsConversationMode, ElevenLabsVADConfig, ElevenLabsClientTool, } from './realtime/index.js';
3
+ export { ElevenLabsSpeechAdapter, createElevenLabsSpeech, elevenlabsSpeech, type ElevenLabsSpeechProviderOptions, type ElevenLabsVoiceSettings, } from './adapters/speech.js';
4
+ export { ElevenLabsAudioAdapter, createElevenLabsAudio, elevenlabsAudio, type ElevenLabsAudioProviderOptions, type ElevenLabsMusicProviderOptions, type ElevenLabsSoundEffectsProviderOptions, type ElevenLabsMusicCompositionPlan, } from './adapters/audio.js';
5
+ export { ElevenLabsTranscriptionAdapter, createElevenLabsTranscription, elevenlabsTranscription, type ElevenLabsTranscriptionProviderOptions, } from './adapters/transcription.js';
6
+ export { ELEVENLABS_TTS_MODELS, ELEVENLABS_AUDIO_MODELS, ELEVENLABS_TRANSCRIPTION_MODELS, isElevenLabsMusicModel, isElevenLabsSoundEffectsModel, type ElevenLabsTTSModel, type ElevenLabsAudioModel, type ElevenLabsMusicModel, type ElevenLabsSoundEffectsModel, type ElevenLabsTranscriptionModel, type ElevenLabsOutputFormat, } from './model-meta.js';
7
+ export { getElevenLabsApiKeyFromEnv, type ElevenLabsClientConfig, } from './utils/index.js';
package/dist/esm/index.js CHANGED
@@ -1,7 +1,27 @@
1
1
  import { elevenlabsRealtimeToken } from "./realtime/token.js";
2
2
  import { elevenlabsRealtime } from "./realtime/adapter.js";
3
+ import { ElevenLabsSpeechAdapter, createElevenLabsSpeech, elevenlabsSpeech } from "./adapters/speech.js";
4
+ import { ElevenLabsAudioAdapter, createElevenLabsAudio, elevenlabsAudio } from "./adapters/audio.js";
5
+ import { ElevenLabsTranscriptionAdapter, createElevenLabsTranscription, elevenlabsTranscription } from "./adapters/transcription.js";
6
+ import { ELEVENLABS_AUDIO_MODELS, ELEVENLABS_TRANSCRIPTION_MODELS, ELEVENLABS_TTS_MODELS, isElevenLabsMusicModel, isElevenLabsSoundEffectsModel } from "./model-meta.js";
7
+ import { getElevenLabsApiKeyFromEnv } from "./utils/client.js";
3
8
  export {
9
+ ELEVENLABS_AUDIO_MODELS,
10
+ ELEVENLABS_TRANSCRIPTION_MODELS,
11
+ ELEVENLABS_TTS_MODELS,
12
+ ElevenLabsAudioAdapter,
13
+ ElevenLabsSpeechAdapter,
14
+ ElevenLabsTranscriptionAdapter,
15
+ createElevenLabsAudio,
16
+ createElevenLabsSpeech,
17
+ createElevenLabsTranscription,
18
+ elevenlabsAudio,
4
19
  elevenlabsRealtime,
5
- elevenlabsRealtimeToken
20
+ elevenlabsRealtimeToken,
21
+ elevenlabsSpeech,
22
+ elevenlabsTranscription,
23
+ getElevenLabsApiKeyFromEnv,
24
+ isElevenLabsMusicModel,
25
+ isElevenLabsSoundEffectsModel
6
26
  };
7
27
  //# sourceMappingURL=index.js.map
@@ -1 +1 @@
1
- {"version":3,"file":"index.js","sources":[],"sourcesContent":[],"names":[],"mappings":";;"}
1
+ {"version":3,"file":"index.js","sources":[],"sourcesContent":[],"names":[],"mappings":";;;;;;;"}
@@ -0,0 +1,42 @@
1
+ import { ElevenLabs } from '@elevenlabs/elevenlabs-js';
2
+ /**
3
+ * ElevenLabs model identifiers. The lists below are the source of truth —
4
+ * callers are blocked from passing unknown model IDs. Keep them in sync with
5
+ * the ElevenLabs SDK via the automated update pipeline.
6
+ */
7
+ /**
8
+ * Text-to-speech models.
9
+ * @see https://elevenlabs.io/docs/models
10
+ */
11
+ export declare const ELEVENLABS_TTS_MODELS: readonly ["eleven_v3", "eleven_multilingual_v2", "eleven_flash_v2_5", "eleven_flash_v2", "eleven_turbo_v2_5", "eleven_turbo_v2", "eleven_monolingual_v1"];
12
+ export type ElevenLabsTTSModel = (typeof ELEVENLABS_TTS_MODELS)[number];
13
+ /**
14
+ * Audio generation models — music (`music_v1`) + sound effects
15
+ * (`eleven_text_to_sound_v*`) share one `generateAudio` adapter.
16
+ * The adapter dispatches by model id so callers pick behavior via the model.
17
+ *
18
+ * @see https://elevenlabs.io/docs/overview/capabilities/music
19
+ * @see https://elevenlabs.io/docs/overview/capabilities/sound-effects
20
+ */
21
+ export declare const ELEVENLABS_AUDIO_MODELS: readonly ["music_v1", "eleven_text_to_sound_v2", "eleven_text_to_sound_v1"];
22
+ export type ElevenLabsAudioModel = (typeof ELEVENLABS_AUDIO_MODELS)[number];
23
+ /** Music models within the audio family. */
24
+ export type ElevenLabsMusicModel = 'music_v1';
25
+ /** SFX models within the audio family. */
26
+ export type ElevenLabsSoundEffectsModel = 'eleven_text_to_sound_v2' | 'eleven_text_to_sound_v1';
27
+ export declare function isElevenLabsMusicModel(model: string): model is ElevenLabsMusicModel;
28
+ export declare function isElevenLabsSoundEffectsModel(model: string): model is ElevenLabsSoundEffectsModel;
29
+ /**
30
+ * Speech-to-text (transcription) models — Scribe family.
31
+ * @see https://elevenlabs.io/docs/overview/capabilities/speech-to-text
32
+ */
33
+ export declare const ELEVENLABS_TRANSCRIPTION_MODELS: readonly ["scribe_v2", "scribe_v1"];
34
+ export type ElevenLabsTranscriptionModel = (typeof ELEVENLABS_TRANSCRIPTION_MODELS)[number];
35
+ /**
36
+ * Supported `output_format` strings, encoded as `codec_samplerate[_bitrate]`.
37
+ * Aliased to the SDK's `AllowedOutputFormats` so the list stays in sync
38
+ * automatically whenever the `@elevenlabs/elevenlabs-js` dependency is bumped.
39
+ *
40
+ * @see https://elevenlabs.io/docs/api-reference/text-to-speech/convert
41
+ */
42
+ export type ElevenLabsOutputFormat = ElevenLabs.AllowedOutputFormats;
@@ -0,0 +1,32 @@
1
+ const ELEVENLABS_TTS_MODELS = [
2
+ "eleven_v3",
3
+ "eleven_multilingual_v2",
4
+ "eleven_flash_v2_5",
5
+ "eleven_flash_v2",
6
+ "eleven_turbo_v2_5",
7
+ "eleven_turbo_v2",
8
+ "eleven_monolingual_v1"
9
+ ];
10
+ const ELEVENLABS_AUDIO_MODELS = [
11
+ "music_v1",
12
+ "eleven_text_to_sound_v2",
13
+ "eleven_text_to_sound_v1"
14
+ ];
15
+ function isElevenLabsMusicModel(model) {
16
+ return model === "music_v1";
17
+ }
18
+ function isElevenLabsSoundEffectsModel(model) {
19
+ return model.startsWith("eleven_text_to_sound_");
20
+ }
21
+ const ELEVENLABS_TRANSCRIPTION_MODELS = [
22
+ "scribe_v2",
23
+ "scribe_v1"
24
+ ];
25
+ export {
26
+ ELEVENLABS_AUDIO_MODELS,
27
+ ELEVENLABS_TRANSCRIPTION_MODELS,
28
+ ELEVENLABS_TTS_MODELS,
29
+ isElevenLabsMusicModel,
30
+ isElevenLabsSoundEffectsModel
31
+ };
32
+ //# sourceMappingURL=model-meta.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"model-meta.js","sources":["../../src/model-meta.ts"],"sourcesContent":["import type { ElevenLabs } from '@elevenlabs/elevenlabs-js'\n\n/**\n * ElevenLabs model identifiers. The lists below are the source of truth —\n * callers are blocked from passing unknown model IDs. Keep them in sync with\n * the ElevenLabs SDK via the automated update pipeline.\n */\n\n/**\n * Text-to-speech models.\n * @see https://elevenlabs.io/docs/models\n */\nexport const ELEVENLABS_TTS_MODELS = [\n 'eleven_v3',\n 'eleven_multilingual_v2',\n 'eleven_flash_v2_5',\n 'eleven_flash_v2',\n 'eleven_turbo_v2_5',\n 'eleven_turbo_v2',\n 'eleven_monolingual_v1',\n] as const\n\nexport type ElevenLabsTTSModel = (typeof ELEVENLABS_TTS_MODELS)[number]\n\n/**\n * Audio generation models — music (`music_v1`) + sound effects\n * (`eleven_text_to_sound_v*`) share one `generateAudio` adapter.\n * The adapter dispatches by model id so callers pick behavior via the model.\n *\n * @see https://elevenlabs.io/docs/overview/capabilities/music\n * @see https://elevenlabs.io/docs/overview/capabilities/sound-effects\n */\nexport const ELEVENLABS_AUDIO_MODELS = [\n 'music_v1',\n 'eleven_text_to_sound_v2',\n 'eleven_text_to_sound_v1',\n] as const\n\nexport type ElevenLabsAudioModel = (typeof ELEVENLABS_AUDIO_MODELS)[number]\n\n/** Music models within the audio family. */\nexport type ElevenLabsMusicModel = 'music_v1'\n/** SFX models within the audio family. */\nexport type ElevenLabsSoundEffectsModel =\n | 'eleven_text_to_sound_v2'\n | 'eleven_text_to_sound_v1'\n\nexport function isElevenLabsMusicModel(\n model: string,\n): model is ElevenLabsMusicModel {\n return model === 'music_v1'\n}\n\nexport function isElevenLabsSoundEffectsModel(\n model: string,\n): model is ElevenLabsSoundEffectsModel {\n return model.startsWith('eleven_text_to_sound_')\n}\n\n/**\n * Speech-to-text (transcription) models — Scribe family.\n * @see https://elevenlabs.io/docs/overview/capabilities/speech-to-text\n */\nexport const ELEVENLABS_TRANSCRIPTION_MODELS = [\n 'scribe_v2',\n 'scribe_v1',\n] as const\n\nexport type ElevenLabsTranscriptionModel =\n (typeof ELEVENLABS_TRANSCRIPTION_MODELS)[number]\n\n/**\n * Supported `output_format` strings, encoded as `codec_samplerate[_bitrate]`.\n * Aliased to the SDK's `AllowedOutputFormats` so the list stays in sync\n * automatically whenever the `@elevenlabs/elevenlabs-js` dependency is bumped.\n *\n * @see https://elevenlabs.io/docs/api-reference/text-to-speech/convert\n */\nexport type ElevenLabsOutputFormat = ElevenLabs.AllowedOutputFormats\n"],"names":[],"mappings":"AAYO,MAAM,wBAAwB;AAAA,EACnC;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AACF;AAYO,MAAM,0BAA0B;AAAA,EACrC;AAAA,EACA;AAAA,EACA;AACF;AAWO,SAAS,uBACd,OAC+B;AAC/B,SAAO,UAAU;AACnB;AAEO,SAAS,8BACd,OACsC;AACtC,SAAO,MAAM,WAAW,uBAAuB;AACjD;AAMO,MAAM,kCAAkC;AAAA,EAC7C;AAAA,EACA;AACF;"}
@@ -3,7 +3,7 @@ import { ElevenLabsRealtimeOptions } from './types.js';
3
3
  /**
4
4
  * Creates an ElevenLabs realtime adapter for client-side use.
5
5
  *
6
- * Wraps the @11labs/client SDK for voice conversations.
6
+ * Wraps the @elevenlabs/client SDK for voice conversations.
7
7
  *
8
8
  * @param options - Optional configuration
9
9
  * @returns A RealtimeAdapter for use with RealtimeClient
@@ -1,4 +1,4 @@
1
- import { Conversation } from "@11labs/client";
1
+ import { Conversation } from "@elevenlabs/client";
2
2
  import { resolveDebugOption } from "@tanstack/ai/adapter-internals";
3
3
  function elevenlabsRealtime(options = {}) {
4
4
  const logger = resolveDebugOption(options.debug);
@@ -1 +1 @@
1
- {"version":3,"file":"adapter.js","sources":["../../../src/realtime/adapter.ts"],"sourcesContent":["import { Conversation } from '@11labs/client'\nimport { resolveDebugOption } from '@tanstack/ai/adapter-internals'\nimport type {\n AnyClientTool,\n AudioVisualization,\n RealtimeEvent,\n RealtimeEventHandler,\n RealtimeMessage,\n RealtimeMode,\n RealtimeSessionConfig,\n RealtimeStatus,\n RealtimeToken,\n} from '@tanstack/ai'\nimport type { InternalLogger } from '@tanstack/ai/adapter-internals'\nimport type { RealtimeAdapter, RealtimeConnection } from '@tanstack/ai-client'\nimport type { ElevenLabsRealtimeOptions } from './types'\n\n/**\n * Creates an ElevenLabs realtime adapter for client-side use.\n *\n * Wraps the @11labs/client SDK for voice conversations.\n *\n * @param options - Optional configuration\n * @returns A RealtimeAdapter for use with RealtimeClient\n *\n * @example\n * ```typescript\n * import { RealtimeClient } from '@tanstack/ai-client'\n * import { elevenlabsRealtime } from '@tanstack/ai-elevenlabs'\n *\n * const client = new RealtimeClient({\n * getToken: () => fetch('/api/realtime-token').then(r => r.json()),\n * adapter: elevenlabsRealtime(),\n * })\n * ```\n */\nexport function elevenlabsRealtime(\n options: ElevenLabsRealtimeOptions = {},\n): RealtimeAdapter {\n const logger = resolveDebugOption(options.debug)\n\n return {\n provider: 'elevenlabs',\n\n async connect(\n token: RealtimeToken,\n clientToolDefs?: ReadonlyArray<AnyClientTool>,\n ): Promise<RealtimeConnection> {\n logger.request(`activity=realtime provider=elevenlabs`, {\n provider: 'elevenlabs',\n })\n return createElevenLabsConnection(token, options, logger, clientToolDefs)\n },\n }\n}\n\n/**\n * Creates a connection to ElevenLabs conversational AI\n */\nasync function createElevenLabsConnection(\n token: RealtimeToken,\n _options: ElevenLabsRealtimeOptions,\n logger: InternalLogger,\n clientToolDefs?: ReadonlyArray<AnyClientTool>,\n): Promise<RealtimeConnection> {\n const eventHandlers = new Map<RealtimeEvent, Set<RealtimeEventHandler<any>>>()\n let conversation: Awaited<\n ReturnType<typeof Conversation.startSession>\n > | null = null\n let messageIdCounter = 0\n\n // Empty arrays for when visualization isn't available\n const emptyFrequencyData = new Uint8Array(128)\n const emptyTimeDomainData = new Uint8Array(128).fill(128)\n\n // Helper to emit events\n function emit<TEvent extends RealtimeEvent>(\n event: TEvent,\n payload: Parameters<RealtimeEventHandler<TEvent>>[0],\n ) {\n const handlers = eventHandlers.get(event)\n if (handlers) {\n for (const handler of handlers) {\n handler(payload)\n }\n }\n }\n\n function generateMessageId(): string {\n return `el-msg-${Date.now()}-${++messageIdCounter}`\n }\n\n // Convert TanStack tool definitions to ElevenLabs clientTools format.\n // @11labs/client@0.2.0 expects plain async functions, not objects.\n const elevenLabsClientTools: Record<\n string,\n (params: unknown) => Promise<string>\n > = {}\n\n if (clientToolDefs) {\n for (const tool of clientToolDefs) {\n elevenLabsClientTools[tool.name] = async (params: unknown) => {\n if (tool.execute) {\n const result = await tool.execute(params)\n return typeof result === 'string' ? result : JSON.stringify(result)\n }\n return JSON.stringify({\n error: `No execute function for tool ${tool.name}`,\n })\n }\n }\n }\n\n // Build session options\n const sessionOptions: Record<string, unknown> = {\n signedUrl: token.token,\n\n onConnect: () => {\n logger.provider(`provider=elevenlabs direction=in type=connect`, {\n frame: { type: 'connect' },\n })\n emit('status_change', { status: 'connected' as RealtimeStatus })\n emit('mode_change', { mode: 'listening' })\n },\n\n onDisconnect: () => {\n logger.provider(`provider=elevenlabs direction=in type=disconnect`, {\n frame: { type: 'disconnect' },\n })\n emit('status_change', { status: 'idle' as RealtimeStatus })\n emit('mode_change', { mode: 'idle' })\n },\n\n onModeChange: ({ mode }: { mode: string }) => {\n logger.provider(`provider=elevenlabs direction=in type=mode_change`, {\n frame: { type: 'mode_change', mode },\n })\n const mappedMode: RealtimeMode =\n mode === 'speaking' ? 'speaking' : 'listening'\n emit('mode_change', { mode: mappedMode })\n },\n\n onMessage: ({ message, source }: { message: string; source: string }) => {\n logger.provider(`provider=elevenlabs direction=in type=message`, {\n frame: { type: 'message', source, message },\n })\n const role = source === 'user' ? 'user' : 'assistant'\n\n if (role === 'user') {\n // User transcripts: only emit transcript — the RealtimeClient\n // creates the user message when it receives a final transcript\n emit('transcript', {\n role,\n transcript: message,\n isFinal: true,\n })\n } else {\n // Assistant messages: only emit message_complete — the canonical\n // event for finalized assistant messages\n const realtimeMessage: RealtimeMessage = {\n id: generateMessageId(),\n role,\n timestamp: Date.now(),\n parts: [{ type: 'audio', transcript: message }],\n }\n emit('message_complete', { message: realtimeMessage })\n }\n },\n\n onError: (error: string | Error) => {\n logger.errors('elevenlabs.realtime fatal', {\n error,\n source: 'elevenlabs.realtime',\n })\n emit('error', {\n error: new Error(\n typeof error === 'string' ? error : error.message || 'Unknown error',\n ),\n })\n },\n }\n\n // Only add clientTools if we have any\n if (Object.keys(elevenLabsClientTools).length > 0) {\n sessionOptions.clientTools = elevenLabsClientTools\n }\n\n // Start the conversation session\n try {\n conversation = await Conversation.startSession(\n sessionOptions as Parameters<typeof Conversation.startSession>[0],\n )\n } catch (error) {\n logger.errors('elevenlabs.realtime fatal', {\n error,\n source: 'elevenlabs.realtime',\n })\n throw error\n }\n\n // Connection implementation\n const connection: RealtimeConnection = {\n async disconnect() {\n if (conversation) {\n await conversation.endSession()\n conversation = null\n }\n emit('status_change', { status: 'idle' as RealtimeStatus })\n },\n\n async startAudioCapture() {\n // ElevenLabs SDK handles audio capture automatically\n // This is called when the session starts\n emit('mode_change', { mode: 'listening' })\n },\n\n stopAudioCapture() {\n // ElevenLabs SDK handles this\n emit('mode_change', { mode: 'idle' })\n },\n\n sendText(text: string) {\n if (!conversation) return\n logger.provider(`provider=elevenlabs direction=out type=user_message`, {\n frame: { type: 'user_message', text },\n })\n conversation.sendUserMessage(text)\n },\n\n sendImage(_imageData: string, _mimeType: string) {\n // ElevenLabs does not support direct image input in the conversation API\n logger.errors('elevenlabs.realtime sendImage not supported', {\n source: 'elevenlabs.realtime',\n })\n },\n\n sendToolResult(_callId: string, _result: string) {\n // ElevenLabs client tools are handled via the clientTools handlers\n // registered at session start — results are returned automatically\n },\n\n updateSession(_config: Partial<RealtimeSessionConfig>) {\n // ElevenLabs session config is set at creation time\n logger.errors('elevenlabs.realtime updateSession not supported', {\n source: 'elevenlabs.realtime',\n })\n },\n\n interrupt() {\n // ElevenLabs handles interruption automatically via barge-in\n // No explicit API to call\n emit('mode_change', { mode: 'listening' })\n emit('interrupted', {})\n },\n\n on<TEvent extends RealtimeEvent>(\n event: TEvent,\n handler: RealtimeEventHandler<TEvent>,\n ): () => void {\n if (!eventHandlers.has(event)) {\n eventHandlers.set(event, new Set())\n }\n eventHandlers.get(event)!.add(handler)\n\n return () => {\n eventHandlers.get(event)?.delete(handler)\n }\n },\n\n getAudioVisualization(): AudioVisualization {\n return {\n get inputLevel() {\n if (!conversation) return 0\n try {\n return conversation.getInputVolume()\n } catch {\n return 0\n }\n },\n\n get outputLevel() {\n if (!conversation) return 0\n try {\n return conversation.getOutputVolume()\n } catch {\n return 0\n }\n },\n\n getInputFrequencyData() {\n if (!conversation) return emptyFrequencyData\n try {\n return conversation.getInputByteFrequencyData()\n } catch {\n return emptyFrequencyData\n }\n },\n\n getOutputFrequencyData() {\n if (!conversation) return emptyFrequencyData\n try {\n return conversation.getOutputByteFrequencyData()\n } catch {\n return emptyFrequencyData\n }\n },\n\n getInputTimeDomainData() {\n // ElevenLabs SDK doesn't expose time domain data\n return emptyTimeDomainData\n },\n\n getOutputTimeDomainData() {\n // ElevenLabs SDK doesn't expose time domain data\n return emptyTimeDomainData\n },\n\n get inputSampleRate() {\n return 16000\n },\n\n get outputSampleRate() {\n return 16000\n },\n }\n },\n }\n\n return connection\n}\n"],"names":[],"mappings":"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"}
1
+ {"version":3,"file":"adapter.js","sources":["../../../src/realtime/adapter.ts"],"sourcesContent":["import { Conversation } from '@elevenlabs/client'\nimport { resolveDebugOption } from '@tanstack/ai/adapter-internals'\nimport type {\n AnyClientTool,\n AudioVisualization,\n RealtimeEvent,\n RealtimeEventHandler,\n RealtimeMessage,\n RealtimeMode,\n RealtimeSessionConfig,\n RealtimeStatus,\n RealtimeToken,\n} from '@tanstack/ai'\nimport type { InternalLogger } from '@tanstack/ai/adapter-internals'\nimport type { RealtimeAdapter, RealtimeConnection } from '@tanstack/ai-client'\nimport type { ElevenLabsRealtimeOptions } from './types'\n\n/**\n * Creates an ElevenLabs realtime adapter for client-side use.\n *\n * Wraps the @elevenlabs/client SDK for voice conversations.\n *\n * @param options - Optional configuration\n * @returns A RealtimeAdapter for use with RealtimeClient\n *\n * @example\n * ```typescript\n * import { RealtimeClient } from '@tanstack/ai-client'\n * import { elevenlabsRealtime } from '@tanstack/ai-elevenlabs'\n *\n * const client = new RealtimeClient({\n * getToken: () => fetch('/api/realtime-token').then(r => r.json()),\n * adapter: elevenlabsRealtime(),\n * })\n * ```\n */\nexport function elevenlabsRealtime(\n options: ElevenLabsRealtimeOptions = {},\n): RealtimeAdapter {\n const logger = resolveDebugOption(options.debug)\n\n return {\n provider: 'elevenlabs',\n\n async connect(\n token: RealtimeToken,\n clientToolDefs?: ReadonlyArray<AnyClientTool>,\n ): Promise<RealtimeConnection> {\n logger.request(`activity=realtime provider=elevenlabs`, {\n provider: 'elevenlabs',\n })\n return createElevenLabsConnection(token, options, logger, clientToolDefs)\n },\n }\n}\n\n/**\n * Creates a connection to ElevenLabs conversational AI\n */\nasync function createElevenLabsConnection(\n token: RealtimeToken,\n _options: ElevenLabsRealtimeOptions,\n logger: InternalLogger,\n clientToolDefs?: ReadonlyArray<AnyClientTool>,\n): Promise<RealtimeConnection> {\n const eventHandlers = new Map<RealtimeEvent, Set<RealtimeEventHandler<any>>>()\n let conversation: Awaited<\n ReturnType<typeof Conversation.startSession>\n > | null = null\n let messageIdCounter = 0\n\n // Empty arrays for when visualization isn't available\n const emptyFrequencyData = new Uint8Array(128)\n const emptyTimeDomainData = new Uint8Array(128).fill(128)\n\n // Helper to emit events\n function emit<TEvent extends RealtimeEvent>(\n event: TEvent,\n payload: Parameters<RealtimeEventHandler<TEvent>>[0],\n ) {\n const handlers = eventHandlers.get(event)\n if (handlers) {\n for (const handler of handlers) {\n handler(payload)\n }\n }\n }\n\n function generateMessageId(): string {\n return `el-msg-${Date.now()}-${++messageIdCounter}`\n }\n\n // Convert TanStack tool definitions to ElevenLabs clientTools format.\n // @elevenlabs/client expects plain async functions, not objects.\n const elevenLabsClientTools: Record<\n string,\n (params: unknown) => Promise<string>\n > = {}\n\n if (clientToolDefs) {\n for (const tool of clientToolDefs) {\n elevenLabsClientTools[tool.name] = async (params: unknown) => {\n if (tool.execute) {\n const result = await tool.execute(params)\n return typeof result === 'string' ? result : JSON.stringify(result)\n }\n return JSON.stringify({\n error: `No execute function for tool ${tool.name}`,\n })\n }\n }\n }\n\n // Build session options\n const sessionOptions: Record<string, unknown> = {\n signedUrl: token.token,\n\n onConnect: () => {\n logger.provider(`provider=elevenlabs direction=in type=connect`, {\n frame: { type: 'connect' },\n })\n emit('status_change', { status: 'connected' as RealtimeStatus })\n emit('mode_change', { mode: 'listening' })\n },\n\n onDisconnect: () => {\n logger.provider(`provider=elevenlabs direction=in type=disconnect`, {\n frame: { type: 'disconnect' },\n })\n emit('status_change', { status: 'idle' as RealtimeStatus })\n emit('mode_change', { mode: 'idle' })\n },\n\n onModeChange: ({ mode }: { mode: string }) => {\n logger.provider(`provider=elevenlabs direction=in type=mode_change`, {\n frame: { type: 'mode_change', mode },\n })\n const mappedMode: RealtimeMode =\n mode === 'speaking' ? 'speaking' : 'listening'\n emit('mode_change', { mode: mappedMode })\n },\n\n onMessage: ({ message, source }: { message: string; source: string }) => {\n logger.provider(`provider=elevenlabs direction=in type=message`, {\n frame: { type: 'message', source, message },\n })\n const role = source === 'user' ? 'user' : 'assistant'\n\n if (role === 'user') {\n // User transcripts: only emit transcript — the RealtimeClient\n // creates the user message when it receives a final transcript\n emit('transcript', {\n role,\n transcript: message,\n isFinal: true,\n })\n } else {\n // Assistant messages: only emit message_complete — the canonical\n // event for finalized assistant messages\n const realtimeMessage: RealtimeMessage = {\n id: generateMessageId(),\n role,\n timestamp: Date.now(),\n parts: [{ type: 'audio', transcript: message }],\n }\n emit('message_complete', { message: realtimeMessage })\n }\n },\n\n onError: (error: string | Error) => {\n logger.errors('elevenlabs.realtime fatal', {\n error,\n source: 'elevenlabs.realtime',\n })\n emit('error', {\n error: new Error(\n typeof error === 'string' ? error : error.message || 'Unknown error',\n ),\n })\n },\n }\n\n // Only add clientTools if we have any\n if (Object.keys(elevenLabsClientTools).length > 0) {\n sessionOptions.clientTools = elevenLabsClientTools\n }\n\n // Start the conversation session\n try {\n conversation = await Conversation.startSession(\n sessionOptions as Parameters<typeof Conversation.startSession>[0],\n )\n } catch (error) {\n logger.errors('elevenlabs.realtime fatal', {\n error,\n source: 'elevenlabs.realtime',\n })\n throw error\n }\n\n // Connection implementation\n const connection: RealtimeConnection = {\n async disconnect() {\n if (conversation) {\n await conversation.endSession()\n conversation = null\n }\n emit('status_change', { status: 'idle' as RealtimeStatus })\n },\n\n async startAudioCapture() {\n // ElevenLabs SDK handles audio capture automatically\n // This is called when the session starts\n emit('mode_change', { mode: 'listening' })\n },\n\n stopAudioCapture() {\n // ElevenLabs SDK handles this\n emit('mode_change', { mode: 'idle' })\n },\n\n sendText(text: string) {\n if (!conversation) return\n logger.provider(`provider=elevenlabs direction=out type=user_message`, {\n frame: { type: 'user_message', text },\n })\n conversation.sendUserMessage(text)\n },\n\n sendImage(_imageData: string, _mimeType: string) {\n // ElevenLabs does not support direct image input in the conversation API\n logger.errors('elevenlabs.realtime sendImage not supported', {\n source: 'elevenlabs.realtime',\n })\n },\n\n sendToolResult(_callId: string, _result: string) {\n // ElevenLabs client tools are handled via the clientTools handlers\n // registered at session start — results are returned automatically\n },\n\n updateSession(_config: Partial<RealtimeSessionConfig>) {\n // ElevenLabs session config is set at creation time\n logger.errors('elevenlabs.realtime updateSession not supported', {\n source: 'elevenlabs.realtime',\n })\n },\n\n interrupt() {\n // ElevenLabs handles interruption automatically via barge-in\n // No explicit API to call\n emit('mode_change', { mode: 'listening' })\n emit('interrupted', {})\n },\n\n on<TEvent extends RealtimeEvent>(\n event: TEvent,\n handler: RealtimeEventHandler<TEvent>,\n ): () => void {\n if (!eventHandlers.has(event)) {\n eventHandlers.set(event, new Set())\n }\n eventHandlers.get(event)!.add(handler)\n\n return () => {\n eventHandlers.get(event)?.delete(handler)\n }\n },\n\n getAudioVisualization(): AudioVisualization {\n return {\n get inputLevel() {\n if (!conversation) return 0\n try {\n return conversation.getInputVolume()\n } catch {\n return 0\n }\n },\n\n get outputLevel() {\n if (!conversation) return 0\n try {\n return conversation.getOutputVolume()\n } catch {\n return 0\n }\n },\n\n getInputFrequencyData() {\n if (!conversation) return emptyFrequencyData\n try {\n return conversation.getInputByteFrequencyData()\n } catch {\n return emptyFrequencyData\n }\n },\n\n getOutputFrequencyData() {\n if (!conversation) return emptyFrequencyData\n try {\n return conversation.getOutputByteFrequencyData()\n } catch {\n return emptyFrequencyData\n }\n },\n\n getInputTimeDomainData() {\n // ElevenLabs SDK doesn't expose time domain data\n return emptyTimeDomainData\n },\n\n getOutputTimeDomainData() {\n // ElevenLabs SDK doesn't expose time domain data\n return emptyTimeDomainData\n },\n\n get inputSampleRate() {\n return 16000\n },\n\n get outputSampleRate() {\n return 16000\n },\n }\n },\n }\n\n return connection\n}\n"],"names":[],"mappings":"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"}
@@ -3,10 +3,12 @@ import { ElevenLabsRealtimeTokenOptions } from './types.js';
3
3
  /**
4
4
  * Creates an ElevenLabs realtime token adapter.
5
5
  *
6
- * This adapter generates signed URLs for client-side connections.
7
- * The signed URL is valid for 30 minutes.
6
+ * Uses the official `@elevenlabs/elevenlabs-js` SDK to request a signed URL
7
+ * for client-side conversation connections. The signed URL is valid for
8
+ * 30 minutes.
8
9
  *
9
- * @param options - Configuration options including agentId
10
+ * @param options - Configuration. `agentId` falls back to
11
+ * `ELEVENLABS_AGENT_ID` in the environment when omitted.
10
12
  * @returns A RealtimeTokenAdapter for use with realtimeToken()
11
13
  *
12
14
  * @example
@@ -14,11 +16,13 @@ import { ElevenLabsRealtimeTokenOptions } from './types.js';
14
16
  * import { realtimeToken } from '@tanstack/ai'
15
17
  * import { elevenlabsRealtimeToken } from '@tanstack/ai-elevenlabs'
16
18
  *
19
+ * // Reads ELEVENLABS_AGENT_ID from env:
20
+ * const token = await realtimeToken({ adapter: elevenlabsRealtimeToken() })
21
+ *
22
+ * // Or pass explicitly:
17
23
  * const token = await realtimeToken({
18
- * adapter: elevenlabsRealtimeToken({
19
- * agentId: 'your-agent-id',
20
- * }),
24
+ * adapter: elevenlabsRealtimeToken({ agentId: 'your-agent-id' }),
21
25
  * })
22
26
  * ```
23
27
  */
24
- export declare function elevenlabsRealtimeToken(options: ElevenLabsRealtimeTokenOptions): RealtimeTokenAdapter;
28
+ export declare function elevenlabsRealtimeToken(options?: ElevenLabsRealtimeTokenOptions): RealtimeTokenAdapter;
@@ -1,42 +1,18 @@
1
- const ELEVENLABS_API_URL = "https://api.elevenlabs.io/v1";
2
- function getElevenLabsApiKey() {
3
- if (typeof process !== "undefined" && process.env.ELEVENLABS_API_KEY) {
4
- return process.env.ELEVENLABS_API_KEY;
5
- }
6
- if (typeof window !== "undefined" && window.env?.ELEVENLABS_API_KEY) {
7
- return window.env.ELEVENLABS_API_KEY;
8
- }
9
- throw new Error(
10
- "ELEVENLABS_API_KEY not found in environment variables. Please set ELEVENLABS_API_KEY in your environment."
11
- );
12
- }
13
- function elevenlabsRealtimeToken(options) {
14
- const apiKey = getElevenLabsApiKey();
1
+ import { createElevenLabsClient, getElevenLabsAgentIdFromEnv } from "../utils/client.js";
2
+ function elevenlabsRealtimeToken(options = {}) {
3
+ const client = createElevenLabsClient();
15
4
  return {
16
5
  provider: "elevenlabs",
17
6
  async generateToken() {
18
- const { agentId, overrides } = options;
19
- const response = await fetch(
20
- `${ELEVENLABS_API_URL}/convai/conversation/get_signed_url?agent_id=${agentId}`,
21
- {
22
- method: "GET",
23
- headers: {
24
- "xi-api-key": apiKey
25
- }
26
- }
7
+ const { overrides } = options;
8
+ const agentId = options.agentId ?? getElevenLabsAgentIdFromEnv();
9
+ const response = await client.conversationalAi.conversations.getSignedUrl(
10
+ { agentId }
27
11
  );
28
- if (!response.ok) {
29
- const errorText = await response.text();
30
- throw new Error(
31
- `ElevenLabs signed URL request failed: ${response.status} ${errorText}`
32
- );
33
- }
34
- const data = await response.json();
35
- const signedUrl = data.signed_url;
36
12
  const expiresAt = Date.now() + 30 * 60 * 1e3;
37
13
  return {
38
14
  provider: "elevenlabs",
39
- token: signedUrl,
15
+ token: response.signedUrl,
40
16
  expiresAt,
41
17
  config: {
42
18
  voice: overrides?.voiceId,
@@ -1 +1 @@
1
- {"version":3,"file":"token.js","sources":["../../../src/realtime/token.ts"],"sourcesContent":["import type { RealtimeToken, RealtimeTokenAdapter } from '@tanstack/ai'\nimport type { ElevenLabsRealtimeTokenOptions } from './types'\n\nconst ELEVENLABS_API_URL = 'https://api.elevenlabs.io/v1'\n\n/**\n * Get ElevenLabs API key from environment\n */\nfunction getElevenLabsApiKey(): string {\n // Check process.env (Node.js)\n if (typeof process !== 'undefined' && process.env.ELEVENLABS_API_KEY) {\n return process.env.ELEVENLABS_API_KEY\n }\n\n // Check window.env (Browser with injected env)\n if (\n typeof window !== 'undefined' &&\n (window as unknown as { env?: { ELEVENLABS_API_KEY?: string } }).env\n ?.ELEVENLABS_API_KEY\n ) {\n return (window as unknown as { env: { ELEVENLABS_API_KEY: string } }).env\n .ELEVENLABS_API_KEY\n }\n\n throw new Error(\n 'ELEVENLABS_API_KEY not found in environment variables. ' +\n 'Please set ELEVENLABS_API_KEY in your environment.',\n )\n}\n\n/**\n * Creates an ElevenLabs realtime token adapter.\n *\n * This adapter generates signed URLs for client-side connections.\n * The signed URL is valid for 30 minutes.\n *\n * @param options - Configuration options including agentId\n * @returns A RealtimeTokenAdapter for use with realtimeToken()\n *\n * @example\n * ```typescript\n * import { realtimeToken } from '@tanstack/ai'\n * import { elevenlabsRealtimeToken } from '@tanstack/ai-elevenlabs'\n *\n * const token = await realtimeToken({\n * adapter: elevenlabsRealtimeToken({\n * agentId: 'your-agent-id',\n * }),\n * })\n * ```\n */\nexport function elevenlabsRealtimeToken(\n options: ElevenLabsRealtimeTokenOptions,\n): RealtimeTokenAdapter {\n const apiKey = getElevenLabsApiKey()\n\n return {\n provider: 'elevenlabs',\n\n async generateToken(): Promise<RealtimeToken> {\n const { agentId, overrides } = options\n\n // Get signed URL from ElevenLabs\n const response = await fetch(\n `${ELEVENLABS_API_URL}/convai/conversation/get_signed_url?agent_id=${agentId}`,\n {\n method: 'GET',\n headers: {\n 'xi-api-key': apiKey,\n },\n },\n )\n\n if (!response.ok) {\n const errorText = await response.text()\n throw new Error(\n `ElevenLabs signed URL request failed: ${response.status} ${errorText}`,\n )\n }\n\n const data = await response.json()\n const signedUrl = data.signed_url as string\n\n // Signed URLs are valid for 30 minutes\n const expiresAt = Date.now() + 30 * 60 * 1000\n\n return {\n provider: 'elevenlabs',\n token: signedUrl,\n expiresAt,\n config: {\n voice: overrides?.voiceId,\n instructions: overrides?.systemPrompt,\n providerOptions: {\n agentId,\n firstMessage: overrides?.firstMessage,\n language: overrides?.language,\n },\n },\n }\n },\n }\n}\n"],"names":[],"mappings":"AAGA,MAAM,qBAAqB;AAK3B,SAAS,sBAA8B;AAErC,MAAI,OAAO,YAAY,eAAe,QAAQ,IAAI,oBAAoB;AACpE,WAAO,QAAQ,IAAI;AAAA,EACrB;AAGA,MACE,OAAO,WAAW,eACjB,OAAgE,KAC7D,oBACJ;AACA,WAAQ,OAA8D,IACnE;AAAA,EACL;AAEA,QAAM,IAAI;AAAA,IACR;AAAA,EAAA;AAGJ;AAuBO,SAAS,wBACd,SACsB;AACtB,QAAM,SAAS,oBAAA;AAEf,SAAO;AAAA,IACL,UAAU;AAAA,IAEV,MAAM,gBAAwC;AAC5C,YAAM,EAAE,SAAS,UAAA,IAAc;AAG/B,YAAM,WAAW,MAAM;AAAA,QACrB,GAAG,kBAAkB,gDAAgD,OAAO;AAAA,QAC5E;AAAA,UACE,QAAQ;AAAA,UACR,SAAS;AAAA,YACP,cAAc;AAAA,UAAA;AAAA,QAChB;AAAA,MACF;AAGF,UAAI,CAAC,SAAS,IAAI;AAChB,cAAM,YAAY,MAAM,SAAS,KAAA;AACjC,cAAM,IAAI;AAAA,UACR,yCAAyC,SAAS,MAAM,IAAI,SAAS;AAAA,QAAA;AAAA,MAEzE;AAEA,YAAM,OAAO,MAAM,SAAS,KAAA;AAC5B,YAAM,YAAY,KAAK;AAGvB,YAAM,YAAY,KAAK,IAAA,IAAQ,KAAK,KAAK;AAEzC,aAAO;AAAA,QACL,UAAU;AAAA,QACV,OAAO;AAAA,QACP;AAAA,QACA,QAAQ;AAAA,UACN,OAAO,WAAW;AAAA,UAClB,cAAc,WAAW;AAAA,UACzB,iBAAiB;AAAA,YACf;AAAA,YACA,cAAc,WAAW;AAAA,YACzB,UAAU,WAAW;AAAA,UAAA;AAAA,QACvB;AAAA,MACF;AAAA,IAEJ;AAAA,EAAA;AAEJ;"}
1
+ {"version":3,"file":"token.js","sources":["../../../src/realtime/token.ts"],"sourcesContent":["import {\n createElevenLabsClient,\n getElevenLabsAgentIdFromEnv,\n} from '../utils/client'\nimport type { RealtimeToken, RealtimeTokenAdapter } from '@tanstack/ai'\nimport type { ElevenLabsRealtimeTokenOptions } from './types'\n\n/**\n * Creates an ElevenLabs realtime token adapter.\n *\n * Uses the official `@elevenlabs/elevenlabs-js` SDK to request a signed URL\n * for client-side conversation connections. The signed URL is valid for\n * 30 minutes.\n *\n * @param options - Configuration. `agentId` falls back to\n * `ELEVENLABS_AGENT_ID` in the environment when omitted.\n * @returns A RealtimeTokenAdapter for use with realtimeToken()\n *\n * @example\n * ```typescript\n * import { realtimeToken } from '@tanstack/ai'\n * import { elevenlabsRealtimeToken } from '@tanstack/ai-elevenlabs'\n *\n * // Reads ELEVENLABS_AGENT_ID from env:\n * const token = await realtimeToken({ adapter: elevenlabsRealtimeToken() })\n *\n * // Or pass explicitly:\n * const token = await realtimeToken({\n * adapter: elevenlabsRealtimeToken({ agentId: 'your-agent-id' }),\n * })\n * ```\n */\nexport function elevenlabsRealtimeToken(\n options: ElevenLabsRealtimeTokenOptions = {},\n): RealtimeTokenAdapter {\n const client = createElevenLabsClient()\n\n return {\n provider: 'elevenlabs',\n\n async generateToken(): Promise<RealtimeToken> {\n const { overrides } = options\n const agentId = options.agentId ?? getElevenLabsAgentIdFromEnv()\n\n const response = await client.conversationalAi.conversations.getSignedUrl(\n { agentId },\n )\n\n // Signed URLs are valid for 30 minutes\n const expiresAt = Date.now() + 30 * 60 * 1000\n\n return {\n provider: 'elevenlabs',\n token: response.signedUrl,\n expiresAt,\n config: {\n voice: overrides?.voiceId,\n instructions: overrides?.systemPrompt,\n providerOptions: {\n agentId,\n firstMessage: overrides?.firstMessage,\n language: overrides?.language,\n },\n },\n }\n },\n }\n}\n"],"names":[],"mappings":";AAgCO,SAAS,wBACd,UAA0C,IACpB;AACtB,QAAM,SAAS,uBAAA;AAEf,SAAO;AAAA,IACL,UAAU;AAAA,IAEV,MAAM,gBAAwC;AAC5C,YAAM,EAAE,cAAc;AACtB,YAAM,UAAU,QAAQ,WAAW,4BAAA;AAEnC,YAAM,WAAW,MAAM,OAAO,iBAAiB,cAAc;AAAA,QAC3D,EAAE,QAAA;AAAA,MAAQ;AAIZ,YAAM,YAAY,KAAK,IAAA,IAAQ,KAAK,KAAK;AAEzC,aAAO;AAAA,QACL,UAAU;AAAA,QACV,OAAO,SAAS;AAAA,QAChB;AAAA,QACA,QAAQ;AAAA,UACN,OAAO,WAAW;AAAA,UAClB,cAAc,WAAW;AAAA,UACzB,iBAAiB;AAAA,YACf;AAAA,YACA,cAAc,WAAW;AAAA,YACzB,UAAU,WAAW;AAAA,UAAA;AAAA,QACvB;AAAA,MACF;AAAA,IAEJ;AAAA,EAAA;AAEJ;"}
@@ -3,8 +3,11 @@ import { DebugOption } from '@tanstack/ai';
3
3
  * Options for the ElevenLabs realtime token adapter
4
4
  */
5
5
  export interface ElevenLabsRealtimeTokenOptions {
6
- /** Agent ID configured in ElevenLabs dashboard */
7
- agentId: string;
6
+ /**
7
+ * Agent ID configured in ElevenLabs dashboard. Falls back to
8
+ * `ELEVENLABS_AGENT_ID` in the environment when omitted.
9
+ */
10
+ agentId?: string;
8
11
  /** Optional override values for the agent */
9
12
  overrides?: {
10
13
  /** Custom voice ID to use */
@@ -0,0 +1,62 @@
1
+ import { ElevenLabsClient } from '@elevenlabs/elevenlabs-js';
2
+ import { ElevenLabsOutputFormat } from '../model-meta.js';
3
+ /**
4
+ * Configuration for any ElevenLabs adapter. When `apiKey` is omitted we read
5
+ * `ELEVENLABS_API_KEY` from `process.env` / `window.env` to match the
6
+ * pattern the realtime adapters already use.
7
+ */
8
+ export interface ElevenLabsClientConfig {
9
+ apiKey?: string;
10
+ /** Override the API base URL — handy for tests + self-hosted proxies. */
11
+ baseUrl?: string;
12
+ /** Per-request timeout passed through to the SDK (ms). */
13
+ timeoutInSeconds?: number;
14
+ /** Override the number of SDK-level retries. */
15
+ maxRetries?: number;
16
+ /** Extra headers attached to every request (e.g. test multiplexing). */
17
+ headers?: Record<string, string>;
18
+ }
19
+ export declare function getElevenLabsApiKeyFromEnv(): string;
20
+ export declare function getElevenLabsAgentIdFromEnv(): string;
21
+ /**
22
+ * Build an `ElevenLabsClient` with env-based or explicit credentials.
23
+ * Each adapter calls this once at construction time so unit tests can
24
+ * pass in an explicit key without needing `process.env`.
25
+ */
26
+ export declare function createElevenLabsClient(config?: ElevenLabsClientConfig): ElevenLabsClient;
27
+ export declare function generateId(prefix: string): string;
28
+ /**
29
+ * Convert an ArrayBuffer to base64 in a cross-runtime way.
30
+ *
31
+ * The naive `btoa(String.fromCharCode(...bytes))` form blows up V8's argument
32
+ * limit (~65k) on realistic audio payloads, so we either use `Buffer`
33
+ * (Node / Bun) or walk the byte array in a single loop (browser).
34
+ */
35
+ export declare function arrayBufferToBase64(buffer: ArrayBuffer): string;
36
+ /**
37
+ * Drain a `ReadableStream<Uint8Array>` (what the ElevenLabs SDK returns for
38
+ * audio endpoints) into a single `Uint8Array`, then expose it as an
39
+ * `ArrayBuffer` slice. We concatenate ourselves rather than going through
40
+ * `new Response(stream).arrayBuffer()` so we stay runtime-agnostic.
41
+ */
42
+ export declare function readStreamToArrayBuffer(stream: ReadableStream<Uint8Array>): Promise<ArrayBuffer>;
43
+ /**
44
+ * Decode a `data:` URL into a Blob for upload to the ElevenLabs STT API
45
+ * (which only accepts multipart files or https URLs). Supports base64 and
46
+ * URL-encoded payloads. Returns `undefined` for non-data-URL strings so the
47
+ * caller can fall through to treating the input as an https URL.
48
+ */
49
+ export declare function dataUrlToBlob(value: string): Blob | undefined;
50
+ /**
51
+ * Break an ElevenLabs `output_format` string (`mp3_44100_128`,
52
+ * `pcm_24000`, `opus_48000_64`, `ulaw_8000`, ...) into a file extension and
53
+ * content-type suitable for `TTSResult` / `AudioGenerationResult` consumers.
54
+ *
55
+ * Unknown codecs fall back to `mp3` / `audio/mpeg` because the ElevenLabs
56
+ * default is `mp3_44100_128` — mispredicting on an exotic format is safer
57
+ * than throwing in the adapter.
58
+ */
59
+ export declare function parseOutputFormat(fmt: ElevenLabsOutputFormat | undefined): {
60
+ format: string;
61
+ contentType: string;
62
+ };
@@ -0,0 +1,124 @@
1
+ import { ElevenLabsClient } from "@elevenlabs/elevenlabs-js";
2
+ function getEnvironment() {
3
+ if (typeof globalThis !== "undefined") {
4
+ const win = globalThis.window;
5
+ if (win?.env) return win.env;
6
+ }
7
+ if (typeof process !== "undefined") {
8
+ return process.env;
9
+ }
10
+ return void 0;
11
+ }
12
+ function getElevenLabsApiKeyFromEnv() {
13
+ const key = getEnvironment()?.ELEVENLABS_API_KEY;
14
+ if (!key) {
15
+ throw new Error(
16
+ "ELEVENLABS_API_KEY is required. Please set it in your environment variables or pass it explicitly to the adapter factory."
17
+ );
18
+ }
19
+ return key;
20
+ }
21
+ function getElevenLabsAgentIdFromEnv() {
22
+ const id = getEnvironment()?.ELEVENLABS_AGENT_ID;
23
+ if (!id) {
24
+ throw new Error(
25
+ "ELEVENLABS_AGENT_ID is required. Please set it in your environment variables or pass `agentId` explicitly to elevenlabsRealtimeToken()."
26
+ );
27
+ }
28
+ return id;
29
+ }
30
+ function createElevenLabsClient(config) {
31
+ const apiKey = config?.apiKey ?? getElevenLabsApiKeyFromEnv();
32
+ return new ElevenLabsClient({
33
+ apiKey,
34
+ ...config?.baseUrl ? { baseUrl: config.baseUrl } : {},
35
+ ...config?.timeoutInSeconds != null ? { timeoutInSeconds: config.timeoutInSeconds } : {},
36
+ ...config?.maxRetries != null ? { maxRetries: config.maxRetries } : {},
37
+ ...config?.headers ? { headers: config.headers } : {}
38
+ });
39
+ }
40
+ function generateId(prefix) {
41
+ return `${prefix}-${Date.now()}-${Math.random().toString(36).substring(2)}`;
42
+ }
43
+ function arrayBufferToBase64(buffer) {
44
+ if (typeof Buffer !== "undefined" && typeof Buffer.from === "function") {
45
+ return Buffer.from(buffer).toString("base64");
46
+ }
47
+ const view = new Uint8Array(buffer);
48
+ let binary = "";
49
+ for (let i = 0; i < view.byteLength; i += 1) {
50
+ binary += String.fromCharCode(view[i]);
51
+ }
52
+ return btoa(binary);
53
+ }
54
+ async function readStreamToArrayBuffer(stream) {
55
+ const reader = stream.getReader();
56
+ const chunks = [];
57
+ let total = 0;
58
+ try {
59
+ let result = await reader.read();
60
+ while (!result.done) {
61
+ chunks.push(result.value);
62
+ total += result.value.byteLength;
63
+ result = await reader.read();
64
+ }
65
+ } finally {
66
+ reader.releaseLock();
67
+ }
68
+ const merged = new Uint8Array(total);
69
+ let offset = 0;
70
+ for (const chunk of chunks) {
71
+ merged.set(chunk, offset);
72
+ offset += chunk.byteLength;
73
+ }
74
+ return merged.buffer.slice(
75
+ merged.byteOffset,
76
+ merged.byteOffset + merged.byteLength
77
+ );
78
+ }
79
+ function dataUrlToBlob(value) {
80
+ if (!value.startsWith("data:")) return void 0;
81
+ const commaIndex = value.indexOf(",");
82
+ if (commaIndex === -1) return void 0;
83
+ const header = value.slice(5, commaIndex);
84
+ const payload = value.slice(commaIndex + 1);
85
+ const isBase64 = /;base64$/i.test(header);
86
+ const mimeType = header.split(";")[0] || "application/octet-stream";
87
+ if (isBase64) {
88
+ const binary = atob(payload);
89
+ const bytes = new Uint8Array(binary.length);
90
+ for (let i = 0; i < binary.length; i += 1) {
91
+ bytes[i] = binary.charCodeAt(i);
92
+ }
93
+ return new Blob([bytes], { type: mimeType });
94
+ }
95
+ return new Blob([decodeURIComponent(payload)], { type: mimeType });
96
+ }
97
+ function parseOutputFormat(fmt) {
98
+ const codec = (fmt || "mp3_44100_128").split("_")[0]?.toLowerCase();
99
+ switch (codec) {
100
+ case "mp3":
101
+ return { format: "mp3", contentType: "audio/mpeg" };
102
+ case "pcm":
103
+ return { format: "pcm", contentType: "audio/pcm" };
104
+ case "opus":
105
+ return { format: "opus", contentType: "audio/opus" };
106
+ case "ulaw":
107
+ return { format: "ulaw", contentType: "audio/basic" };
108
+ case "alaw":
109
+ return { format: "alaw", contentType: "audio/x-alaw-basic" };
110
+ default:
111
+ return { format: "mp3", contentType: "audio/mpeg" };
112
+ }
113
+ }
114
+ export {
115
+ arrayBufferToBase64,
116
+ createElevenLabsClient,
117
+ dataUrlToBlob,
118
+ generateId,
119
+ getElevenLabsAgentIdFromEnv,
120
+ getElevenLabsApiKeyFromEnv,
121
+ parseOutputFormat,
122
+ readStreamToArrayBuffer
123
+ };
124
+ //# sourceMappingURL=client.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"client.js","sources":["../../../src/utils/client.ts"],"sourcesContent":["import { ElevenLabsClient } from '@elevenlabs/elevenlabs-js'\nimport type { ElevenLabsOutputFormat } from '../model-meta'\n\n/**\n * Configuration for any ElevenLabs adapter. When `apiKey` is omitted we read\n * `ELEVENLABS_API_KEY` from `process.env` / `window.env` to match the\n * pattern the realtime adapters already use.\n */\nexport interface ElevenLabsClientConfig {\n apiKey?: string\n /** Override the API base URL — handy for tests + self-hosted proxies. */\n baseUrl?: string\n /** Per-request timeout passed through to the SDK (ms). */\n timeoutInSeconds?: number\n /** Override the number of SDK-level retries. */\n maxRetries?: number\n /** Extra headers attached to every request (e.g. test multiplexing). */\n headers?: Record<string, string>\n}\n\ninterface EnvObject {\n ELEVENLABS_API_KEY?: string\n ELEVENLABS_AGENT_ID?: string\n}\n\ninterface WindowWithEnv {\n env?: EnvObject\n}\n\nfunction getEnvironment(): EnvObject | undefined {\n if (typeof globalThis !== 'undefined') {\n const win = (globalThis as { window?: WindowWithEnv }).window\n if (win?.env) return win.env\n }\n if (typeof process !== 'undefined') {\n return process.env as EnvObject\n }\n return undefined\n}\n\nexport function getElevenLabsApiKeyFromEnv(): string {\n const key = getEnvironment()?.ELEVENLABS_API_KEY\n if (!key) {\n throw new Error(\n 'ELEVENLABS_API_KEY is required. Please set it in your environment ' +\n 'variables or pass it explicitly to the adapter factory.',\n )\n }\n return key\n}\n\nexport function getElevenLabsAgentIdFromEnv(): string {\n const id = getEnvironment()?.ELEVENLABS_AGENT_ID\n if (!id) {\n throw new Error(\n 'ELEVENLABS_AGENT_ID is required. Please set it in your environment ' +\n 'variables or pass `agentId` explicitly to elevenlabsRealtimeToken().',\n )\n }\n return id\n}\n\n/**\n * Build an `ElevenLabsClient` with env-based or explicit credentials.\n * Each adapter calls this once at construction time so unit tests can\n * pass in an explicit key without needing `process.env`.\n */\nexport function createElevenLabsClient(\n config?: ElevenLabsClientConfig,\n): ElevenLabsClient {\n const apiKey = config?.apiKey ?? getElevenLabsApiKeyFromEnv()\n return new ElevenLabsClient({\n apiKey,\n ...(config?.baseUrl ? { baseUrl: config.baseUrl } : {}),\n ...(config?.timeoutInSeconds != null\n ? { timeoutInSeconds: config.timeoutInSeconds }\n : {}),\n ...(config?.maxRetries != null ? { maxRetries: config.maxRetries } : {}),\n ...(config?.headers ? { headers: config.headers } : {}),\n })\n}\n\nexport function generateId(prefix: string): string {\n return `${prefix}-${Date.now()}-${Math.random().toString(36).substring(2)}`\n}\n\n/**\n * Convert an ArrayBuffer to base64 in a cross-runtime way.\n *\n * The naive `btoa(String.fromCharCode(...bytes))` form blows up V8's argument\n * limit (~65k) on realistic audio payloads, so we either use `Buffer`\n * (Node / Bun) or walk the byte array in a single loop (browser).\n */\nexport function arrayBufferToBase64(buffer: ArrayBuffer): string {\n if (typeof Buffer !== 'undefined' && typeof Buffer.from === 'function') {\n return Buffer.from(buffer).toString('base64')\n }\n const view = new Uint8Array(buffer)\n let binary = ''\n for (let i = 0; i < view.byteLength; i += 1) {\n binary += String.fromCharCode(view[i]!)\n }\n return btoa(binary)\n}\n\n/**\n * Drain a `ReadableStream<Uint8Array>` (what the ElevenLabs SDK returns for\n * audio endpoints) into a single `Uint8Array`, then expose it as an\n * `ArrayBuffer` slice. We concatenate ourselves rather than going through\n * `new Response(stream).arrayBuffer()` so we stay runtime-agnostic.\n */\nexport async function readStreamToArrayBuffer(\n stream: ReadableStream<Uint8Array>,\n): Promise<ArrayBuffer> {\n const reader = stream.getReader()\n const chunks: Array<Uint8Array> = []\n let total = 0\n try {\n let result = await reader.read()\n while (!result.done) {\n chunks.push(result.value)\n total += result.value.byteLength\n result = await reader.read()\n }\n } finally {\n reader.releaseLock()\n }\n const merged = new Uint8Array(total)\n let offset = 0\n for (const chunk of chunks) {\n merged.set(chunk, offset)\n offset += chunk.byteLength\n }\n return merged.buffer.slice(\n merged.byteOffset,\n merged.byteOffset + merged.byteLength,\n )\n}\n\n/**\n * Decode a `data:` URL into a Blob for upload to the ElevenLabs STT API\n * (which only accepts multipart files or https URLs). Supports base64 and\n * URL-encoded payloads. Returns `undefined` for non-data-URL strings so the\n * caller can fall through to treating the input as an https URL.\n */\nexport function dataUrlToBlob(value: string): Blob | undefined {\n if (!value.startsWith('data:')) return undefined\n const commaIndex = value.indexOf(',')\n if (commaIndex === -1) return undefined\n\n const header = value.slice(5, commaIndex)\n const payload = value.slice(commaIndex + 1)\n const isBase64 = /;base64$/i.test(header)\n const mimeType = header.split(';')[0] || 'application/octet-stream'\n\n if (isBase64) {\n const binary = atob(payload)\n const bytes = new Uint8Array(binary.length)\n for (let i = 0; i < binary.length; i += 1) {\n bytes[i] = binary.charCodeAt(i)\n }\n return new Blob([bytes], { type: mimeType })\n }\n\n return new Blob([decodeURIComponent(payload)], { type: mimeType })\n}\n\n/**\n * Break an ElevenLabs `output_format` string (`mp3_44100_128`,\n * `pcm_24000`, `opus_48000_64`, `ulaw_8000`, ...) into a file extension and\n * content-type suitable for `TTSResult` / `AudioGenerationResult` consumers.\n *\n * Unknown codecs fall back to `mp3` / `audio/mpeg` because the ElevenLabs\n * default is `mp3_44100_128` — mispredicting on an exotic format is safer\n * than throwing in the adapter.\n */\nexport function parseOutputFormat(fmt: ElevenLabsOutputFormat | undefined): {\n format: string\n contentType: string\n} {\n const codec = (fmt || 'mp3_44100_128').split('_')[0]?.toLowerCase()\n switch (codec) {\n case 'mp3':\n return { format: 'mp3', contentType: 'audio/mpeg' }\n case 'pcm':\n return { format: 'pcm', contentType: 'audio/pcm' }\n case 'opus':\n return { format: 'opus', contentType: 'audio/opus' }\n case 'ulaw':\n return { format: 'ulaw', contentType: 'audio/basic' }\n case 'alaw':\n return { format: 'alaw', contentType: 'audio/x-alaw-basic' }\n default:\n return { format: 'mp3', contentType: 'audio/mpeg' }\n }\n}\n"],"names":[],"mappings":"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"}
@@ -0,0 +1 @@
1
+ export { arrayBufferToBase64, createElevenLabsClient, dataUrlToBlob, generateId, getElevenLabsApiKeyFromEnv, parseOutputFormat, readStreamToArrayBuffer, type ElevenLabsClientConfig, } from './client.js';