@tanstack/ai-elevenlabs 0.1.8 → 0.2.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 +93 -0
- package/dist/esm/adapters/audio.js +106 -0
- package/dist/esm/adapters/audio.js.map +1 -0
- package/dist/esm/adapters/speech.d.ts +83 -0
- package/dist/esm/adapters/speech.js +109 -0
- package/dist/esm/adapters/speech.js.map +1 -0
- package/dist/esm/adapters/transcription.d.ts +79 -0
- package/dist/esm/adapters/transcription.js +142 -0
- package/dist/esm/adapters/transcription.js.map +1 -0
- package/dist/esm/index.d.ts +5 -0
- package/dist/esm/index.js +21 -1
- package/dist/esm/index.js.map +1 -1
- package/dist/esm/model-meta.d.ts +42 -0
- package/dist/esm/model-meta.js +32 -0
- package/dist/esm/model-meta.js.map +1 -0
- package/dist/esm/realtime/adapter.d.ts +1 -1
- package/dist/esm/realtime/adapter.js +1 -1
- package/dist/esm/realtime/adapter.js.map +1 -1
- package/dist/esm/realtime/token.d.ts +11 -7
- package/dist/esm/realtime/token.js +8 -32
- package/dist/esm/realtime/token.js.map +1 -1
- package/dist/esm/realtime/types.d.ts +5 -2
- package/dist/esm/utils/client.d.ts +62 -0
- package/dist/esm/utils/client.js +124 -0
- package/dist/esm/utils/client.js.map +1 -0
- package/dist/esm/utils/index.d.ts +1 -0
- package/package.json +11 -3
- package/src/adapters/audio.ts +257 -0
- package/src/adapters/speech.ts +240 -0
- package/src/adapters/transcription.ts +338 -0
- package/src/index.ts +64 -0
- package/src/model-meta.ts +79 -0
- package/src/realtime/adapter.ts +3 -3
- package/src/realtime/token.ts +21 -56
- package/src/realtime/types.ts +5 -2
- package/src/utils/client.ts +196 -0
- package/src/utils/index.ts +10 -0
|
@@ -0,0 +1,196 @@
|
|
|
1
|
+
import { ElevenLabsClient } from '@elevenlabs/elevenlabs-js'
|
|
2
|
+
import type { ElevenLabsOutputFormat } from '../model-meta'
|
|
3
|
+
|
|
4
|
+
/**
|
|
5
|
+
* Configuration for any ElevenLabs adapter. When `apiKey` is omitted we read
|
|
6
|
+
* `ELEVENLABS_API_KEY` from `process.env` / `window.env` to match the
|
|
7
|
+
* pattern the realtime adapters already use.
|
|
8
|
+
*/
|
|
9
|
+
export interface ElevenLabsClientConfig {
|
|
10
|
+
apiKey?: string
|
|
11
|
+
/** Override the API base URL — handy for tests + self-hosted proxies. */
|
|
12
|
+
baseUrl?: string
|
|
13
|
+
/** Per-request timeout passed through to the SDK (ms). */
|
|
14
|
+
timeoutInSeconds?: number
|
|
15
|
+
/** Override the number of SDK-level retries. */
|
|
16
|
+
maxRetries?: number
|
|
17
|
+
/** Extra headers attached to every request (e.g. test multiplexing). */
|
|
18
|
+
headers?: Record<string, string>
|
|
19
|
+
}
|
|
20
|
+
|
|
21
|
+
interface EnvObject {
|
|
22
|
+
ELEVENLABS_API_KEY?: string
|
|
23
|
+
ELEVENLABS_AGENT_ID?: string
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
interface WindowWithEnv {
|
|
27
|
+
env?: EnvObject
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
function getEnvironment(): EnvObject | undefined {
|
|
31
|
+
if (typeof globalThis !== 'undefined') {
|
|
32
|
+
const win = (globalThis as { window?: WindowWithEnv }).window
|
|
33
|
+
if (win?.env) return win.env
|
|
34
|
+
}
|
|
35
|
+
if (typeof process !== 'undefined') {
|
|
36
|
+
return process.env as EnvObject
|
|
37
|
+
}
|
|
38
|
+
return undefined
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
export function getElevenLabsApiKeyFromEnv(): string {
|
|
42
|
+
const key = getEnvironment()?.ELEVENLABS_API_KEY
|
|
43
|
+
if (!key) {
|
|
44
|
+
throw new Error(
|
|
45
|
+
'ELEVENLABS_API_KEY is required. Please set it in your environment ' +
|
|
46
|
+
'variables or pass it explicitly to the adapter factory.',
|
|
47
|
+
)
|
|
48
|
+
}
|
|
49
|
+
return key
|
|
50
|
+
}
|
|
51
|
+
|
|
52
|
+
export function getElevenLabsAgentIdFromEnv(): string {
|
|
53
|
+
const id = getEnvironment()?.ELEVENLABS_AGENT_ID
|
|
54
|
+
if (!id) {
|
|
55
|
+
throw new Error(
|
|
56
|
+
'ELEVENLABS_AGENT_ID is required. Please set it in your environment ' +
|
|
57
|
+
'variables or pass `agentId` explicitly to elevenlabsRealtimeToken().',
|
|
58
|
+
)
|
|
59
|
+
}
|
|
60
|
+
return id
|
|
61
|
+
}
|
|
62
|
+
|
|
63
|
+
/**
|
|
64
|
+
* Build an `ElevenLabsClient` with env-based or explicit credentials.
|
|
65
|
+
* Each adapter calls this once at construction time so unit tests can
|
|
66
|
+
* pass in an explicit key without needing `process.env`.
|
|
67
|
+
*/
|
|
68
|
+
export function createElevenLabsClient(
|
|
69
|
+
config?: ElevenLabsClientConfig,
|
|
70
|
+
): ElevenLabsClient {
|
|
71
|
+
const apiKey = config?.apiKey ?? getElevenLabsApiKeyFromEnv()
|
|
72
|
+
return new ElevenLabsClient({
|
|
73
|
+
apiKey,
|
|
74
|
+
...(config?.baseUrl ? { baseUrl: config.baseUrl } : {}),
|
|
75
|
+
...(config?.timeoutInSeconds != null
|
|
76
|
+
? { timeoutInSeconds: config.timeoutInSeconds }
|
|
77
|
+
: {}),
|
|
78
|
+
...(config?.maxRetries != null ? { maxRetries: config.maxRetries } : {}),
|
|
79
|
+
...(config?.headers ? { headers: config.headers } : {}),
|
|
80
|
+
})
|
|
81
|
+
}
|
|
82
|
+
|
|
83
|
+
export function generateId(prefix: string): string {
|
|
84
|
+
return `${prefix}-${Date.now()}-${Math.random().toString(36).substring(2)}`
|
|
85
|
+
}
|
|
86
|
+
|
|
87
|
+
/**
|
|
88
|
+
* Convert an ArrayBuffer to base64 in a cross-runtime way.
|
|
89
|
+
*
|
|
90
|
+
* The naive `btoa(String.fromCharCode(...bytes))` form blows up V8's argument
|
|
91
|
+
* limit (~65k) on realistic audio payloads, so we either use `Buffer`
|
|
92
|
+
* (Node / Bun) or walk the byte array in a single loop (browser).
|
|
93
|
+
*/
|
|
94
|
+
export function arrayBufferToBase64(buffer: ArrayBuffer): string {
|
|
95
|
+
if (typeof Buffer !== 'undefined' && typeof Buffer.from === 'function') {
|
|
96
|
+
return Buffer.from(buffer).toString('base64')
|
|
97
|
+
}
|
|
98
|
+
const view = new Uint8Array(buffer)
|
|
99
|
+
let binary = ''
|
|
100
|
+
for (let i = 0; i < view.byteLength; i += 1) {
|
|
101
|
+
binary += String.fromCharCode(view[i]!)
|
|
102
|
+
}
|
|
103
|
+
return btoa(binary)
|
|
104
|
+
}
|
|
105
|
+
|
|
106
|
+
/**
|
|
107
|
+
* Drain a `ReadableStream<Uint8Array>` (what the ElevenLabs SDK returns for
|
|
108
|
+
* audio endpoints) into a single `Uint8Array`, then expose it as an
|
|
109
|
+
* `ArrayBuffer` slice. We concatenate ourselves rather than going through
|
|
110
|
+
* `new Response(stream).arrayBuffer()` so we stay runtime-agnostic.
|
|
111
|
+
*/
|
|
112
|
+
export async function readStreamToArrayBuffer(
|
|
113
|
+
stream: ReadableStream<Uint8Array>,
|
|
114
|
+
): Promise<ArrayBuffer> {
|
|
115
|
+
const reader = stream.getReader()
|
|
116
|
+
const chunks: Array<Uint8Array> = []
|
|
117
|
+
let total = 0
|
|
118
|
+
try {
|
|
119
|
+
let result = await reader.read()
|
|
120
|
+
while (!result.done) {
|
|
121
|
+
chunks.push(result.value)
|
|
122
|
+
total += result.value.byteLength
|
|
123
|
+
result = await reader.read()
|
|
124
|
+
}
|
|
125
|
+
} finally {
|
|
126
|
+
reader.releaseLock()
|
|
127
|
+
}
|
|
128
|
+
const merged = new Uint8Array(total)
|
|
129
|
+
let offset = 0
|
|
130
|
+
for (const chunk of chunks) {
|
|
131
|
+
merged.set(chunk, offset)
|
|
132
|
+
offset += chunk.byteLength
|
|
133
|
+
}
|
|
134
|
+
return merged.buffer.slice(
|
|
135
|
+
merged.byteOffset,
|
|
136
|
+
merged.byteOffset + merged.byteLength,
|
|
137
|
+
)
|
|
138
|
+
}
|
|
139
|
+
|
|
140
|
+
/**
|
|
141
|
+
* Decode a `data:` URL into a Blob for upload to the ElevenLabs STT API
|
|
142
|
+
* (which only accepts multipart files or https URLs). Supports base64 and
|
|
143
|
+
* URL-encoded payloads. Returns `undefined` for non-data-URL strings so the
|
|
144
|
+
* caller can fall through to treating the input as an https URL.
|
|
145
|
+
*/
|
|
146
|
+
export function dataUrlToBlob(value: string): Blob | undefined {
|
|
147
|
+
if (!value.startsWith('data:')) return undefined
|
|
148
|
+
const commaIndex = value.indexOf(',')
|
|
149
|
+
if (commaIndex === -1) return undefined
|
|
150
|
+
|
|
151
|
+
const header = value.slice(5, commaIndex)
|
|
152
|
+
const payload = value.slice(commaIndex + 1)
|
|
153
|
+
const isBase64 = /;base64$/i.test(header)
|
|
154
|
+
const mimeType = header.split(';')[0] || 'application/octet-stream'
|
|
155
|
+
|
|
156
|
+
if (isBase64) {
|
|
157
|
+
const binary = atob(payload)
|
|
158
|
+
const bytes = new Uint8Array(binary.length)
|
|
159
|
+
for (let i = 0; i < binary.length; i += 1) {
|
|
160
|
+
bytes[i] = binary.charCodeAt(i)
|
|
161
|
+
}
|
|
162
|
+
return new Blob([bytes], { type: mimeType })
|
|
163
|
+
}
|
|
164
|
+
|
|
165
|
+
return new Blob([decodeURIComponent(payload)], { type: mimeType })
|
|
166
|
+
}
|
|
167
|
+
|
|
168
|
+
/**
|
|
169
|
+
* Break an ElevenLabs `output_format` string (`mp3_44100_128`,
|
|
170
|
+
* `pcm_24000`, `opus_48000_64`, `ulaw_8000`, ...) into a file extension and
|
|
171
|
+
* content-type suitable for `TTSResult` / `AudioGenerationResult` consumers.
|
|
172
|
+
*
|
|
173
|
+
* Unknown codecs fall back to `mp3` / `audio/mpeg` because the ElevenLabs
|
|
174
|
+
* default is `mp3_44100_128` — mispredicting on an exotic format is safer
|
|
175
|
+
* than throwing in the adapter.
|
|
176
|
+
*/
|
|
177
|
+
export function parseOutputFormat(fmt: ElevenLabsOutputFormat | undefined): {
|
|
178
|
+
format: string
|
|
179
|
+
contentType: string
|
|
180
|
+
} {
|
|
181
|
+
const codec = (fmt || 'mp3_44100_128').split('_')[0]?.toLowerCase()
|
|
182
|
+
switch (codec) {
|
|
183
|
+
case 'mp3':
|
|
184
|
+
return { format: 'mp3', contentType: 'audio/mpeg' }
|
|
185
|
+
case 'pcm':
|
|
186
|
+
return { format: 'pcm', contentType: 'audio/pcm' }
|
|
187
|
+
case 'opus':
|
|
188
|
+
return { format: 'opus', contentType: 'audio/opus' }
|
|
189
|
+
case 'ulaw':
|
|
190
|
+
return { format: 'ulaw', contentType: 'audio/basic' }
|
|
191
|
+
case 'alaw':
|
|
192
|
+
return { format: 'alaw', contentType: 'audio/x-alaw-basic' }
|
|
193
|
+
default:
|
|
194
|
+
return { format: 'mp3', contentType: 'audio/mpeg' }
|
|
195
|
+
}
|
|
196
|
+
}
|