@voiceinput/elevenlabs 0.1.0-beta.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.
package/LICENSE ADDED
@@ -0,0 +1,21 @@
1
+ MIT License
2
+
3
+ Copyright (c) 2026 Hirad Arshadiyarahmadi
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining a copy
6
+ of this software and associated documentation files (the "Software"), to deal
7
+ in the Software without restriction, including without limitation the rights
8
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9
+ copies of the Software, and to permit persons to whom the Software is
10
+ furnished to do so, subject to the following conditions:
11
+
12
+ The above copyright notice and this permission notice shall be included in all
13
+ copies or substantial portions of the Software.
14
+
15
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21
+ SOFTWARE.
package/README.md ADDED
@@ -0,0 +1,114 @@
1
+ # `@voiceinput/elevenlabs`
2
+
3
+ ElevenLabs Realtime Scribe adapter for VoiceInput. The root export runs in the
4
+ browser; `@voiceinput/elevenlabs/server` mints single-use tokens with a
5
+ long-lived API key.
6
+
7
+ ## Install
8
+
9
+ ```bash
10
+ npm install @voiceinput/react@next @voiceinput/elevenlabs@next
11
+ ```
12
+
13
+ ## Browser adapter
14
+
15
+ ```ts
16
+ import { elevenlabs } from "@voiceinput/elevenlabs";
17
+
18
+ const provider = elevenlabs({
19
+ tokenEndpoint: "/api/voice-token",
20
+ vadThreshold: 0.35,
21
+ filterBackgroundAudio: true,
22
+ });
23
+ ```
24
+
25
+ ### Defaults and shared-option mapping
26
+
27
+ - Model: `scribe_v2_realtime` (`ELEVENLABS_DEFAULT_MODEL`)
28
+ - Audio: mono PCM16 at 16 kHz
29
+ - Omitted language: provider automatic detection
30
+ - `language`: normalized to an ISO 639-1 or ISO 639-3 primary code
31
+ - `vocabulary`: ElevenLabs key terms
32
+ - `endpointing`: VAD with a 650 ms silence threshold when omitted, manual commit
33
+ when `false`, or VAD with a supplied 300–3000 ms silence threshold
34
+
35
+ Vocabulary accepts at most 50 trimmed terms, each at most 20 characters and
36
+ without line breaks. Invalid or unsupported settings fail before microphone
37
+ permission with distinct error codes.
38
+
39
+ ### `ElevenLabsVoiceInputProviderOptions`
40
+
41
+ | Option | Purpose |
42
+ | ----------------------------------- | ------------------------------------------------------------- |
43
+ | `tokenEndpoint` | Required same-origin endpoint that returns a single-use token |
44
+ | `model` | Model ID; default `scribe_v2_realtime` |
45
+ | `finishTimeoutMs` | Graceful final-commit deadline; default 4 seconds |
46
+ | `vadThreshold` | VAD threshold from 0.1 to 0.9 |
47
+ | `minSpeechDurationMs` | Integer from 50 to 2000 |
48
+ | `minSilenceDurationMs` | Integer from 50 to 2000 |
49
+ | `noVerbatim` | Provider no-verbatim behavior |
50
+ | `filterBackgroundAudio` | Provider background-audio filtering |
51
+ | `fetch`, `webSocket`, `realtimeUrl` | Transport/endpoint overrides |
52
+
53
+ Provider-only VAD settings customize the VAD behavior used by the portable 650
54
+ ms default.
55
+
56
+ ## Server token handler
57
+
58
+ ```ts
59
+ import { createElevenLabsTokenHandler } from "@voiceinput/elevenlabs/server";
60
+
61
+ export const POST = createElevenLabsTokenHandler({
62
+ apiKey: process.env.ELEVENLABS_API_KEY!,
63
+ authorize: async (request) => {
64
+ const user = await authenticate(request);
65
+ return user ? { subject: user.id } : null;
66
+ },
67
+ });
68
+ ```
69
+
70
+ The handler accepts only `POST`, requires authorization, sets
71
+ `Cache-Control: no-store`, and returns a single-use token rather than the
72
+ long-lived API key. Requests must be JSON and are limited to 16 KiB.
73
+ Authorization and rate-limit callbacks receive independent request bodies. If
74
+ `onTokenIssued` throws, token delivery fails closed.
75
+
76
+ ### `CreateElevenLabsTokenHandlerOptions`
77
+
78
+ | Option | Purpose |
79
+ | ------------------------- | -------------------------------------------------------- |
80
+ | `apiKey` | Required server-only ElevenLabs key |
81
+ | `authorize(request)` | Required application authorization |
82
+ | `model` | Default model; default `scribe_v2_realtime` |
83
+ | `allowedModels` | Browser-selectable models; defaults to only `model` |
84
+ | `rateLimit(context)` | Optional application quota check |
85
+ | `onTokenIssued(metadata)` | Metadata-only callback with provider, subject, and model |
86
+ | `fetch`, `tokenUrl` | Transport/endpoint overrides |
87
+
88
+ `ElevenLabsTokenHandlerContext` contains `request`, `subject`, and `model`.
89
+
90
+ ## Public API
91
+
92
+ Browser root:
93
+
94
+ - `elevenlabs(options)`
95
+ - `ELEVENLABS_DEFAULT_MODEL`
96
+ - `ElevenLabsVoiceInputProviderOptions`
97
+
98
+ Server-only entry point:
99
+
100
+ - `createElevenLabsTokenHandler(options)`
101
+ - `CreateElevenLabsTokenHandlerOptions`
102
+ - `ElevenLabsAuthorization`
103
+ - `ElevenLabsRateLimitResult`
104
+ - `ElevenLabsTokenHandlerContext`
105
+ - `ElevenLabsTokenIssuedMetadata`
106
+
107
+ ## Security
108
+
109
+ Import `/server` only from server code; the export is disabled under the browser
110
+ condition. Never expose `ELEVENLABS_API_KEY` to the client. The browser uses the
111
+ single-use token to stream audio directly to ElevenLabs.
112
+
113
+ See the
114
+ [secure integration guides](https://github.com/VoiceInput/voiceinput/blob/main/docs/nextjs.md).
package/dist/index.cjs ADDED
@@ -0,0 +1,527 @@
1
+ Object.defineProperty(exports, Symbol.toStringTag, { value: "Module" });
2
+ const require_session_config = require("./session-config-D2GQf8TE.cjs");
3
+ let _voiceinput_provider_transport = require("@voiceinput/provider/transport");
4
+ let _voiceinput_provider = require("@voiceinput/provider");
5
+ //#region src/index.ts
6
+ const DEFAULT_REALTIME_URL = "wss://api.elevenlabs.io/v1/speech-to-text/realtime";
7
+ const DEFAULT_FINISH_TIMEOUT_MS = 4e3;
8
+ const FINISH_DRAIN_MS = 250;
9
+ function elevenlabs(options) {
10
+ const model = factoryString(options.model ?? "scribe_v2_realtime", "model");
11
+ const tokenEndpoint = factoryString(String(options.tokenEndpoint), "tokenEndpoint");
12
+ const realtimeUrl = factoryString(options.realtimeUrl ?? DEFAULT_REALTIME_URL, "realtimeUrl");
13
+ const finishTimeoutMs = positiveInteger(options.finishTimeoutMs ?? DEFAULT_FINISH_TIMEOUT_MS, "finishTimeoutMs");
14
+ const providerSettings = {
15
+ ...options.vadThreshold === void 0 ? {} : { vadThreshold: options.vadThreshold },
16
+ ...options.minSpeechDurationMs === void 0 ? {} : { minSpeechDurationMs: options.minSpeechDurationMs },
17
+ ...options.minSilenceDurationMs === void 0 ? {} : { minSilenceDurationMs: options.minSilenceDurationMs },
18
+ ...options.noVerbatim === void 0 ? {} : { noVerbatim: options.noVerbatim },
19
+ ...options.filterBackgroundAudio === void 0 ? {} : { filterBackgroundAudio: options.filterBackgroundAudio }
20
+ };
21
+ const validateProviderOptions = (transcriptionOptions) => {
22
+ try {
23
+ require_session_config.validateElevenLabsConfiguration({
24
+ model,
25
+ ...providerSettings,
26
+ ...transcriptionOptions
27
+ });
28
+ } catch (cause) {
29
+ if (_voiceinput_provider.VoiceInputError.isInstance(cause)) throw cause;
30
+ throw invalidConfiguration(cause);
31
+ }
32
+ };
33
+ validateProviderOptions({});
34
+ return Object.freeze({
35
+ specificationVersion: "v1",
36
+ provider: "elevenlabs",
37
+ modelId: model,
38
+ sampleRate: require_session_config.ELEVENLABS_SAMPLE_RATE,
39
+ validateOptions: validateProviderOptions,
40
+ async doOpen(callOptions) {
41
+ validateProviderOptions(callOptions);
42
+ const configuration = require_session_config.validateElevenLabsConfiguration({
43
+ model,
44
+ ...providerSettings,
45
+ ...callOptions
46
+ });
47
+ return await openSession({
48
+ abortSignal: callOptions.abortSignal,
49
+ configuration,
50
+ fetchImplementation: options.fetch ?? globalThis.fetch,
51
+ finishTimeoutMs,
52
+ realtimeUrl,
53
+ tokenEndpoint,
54
+ WebSocketImplementation: options.webSocket ?? globalThis.WebSocket
55
+ });
56
+ }
57
+ });
58
+ }
59
+ async function openSession(options) {
60
+ throwIfAborted(options.abortSignal);
61
+ requireBrowserFunction(options.fetchImplementation, "fetch");
62
+ requireBrowserFunction(options.WebSocketImplementation, "WebSocket");
63
+ const token = await requestToken(options.fetchImplementation, options.tokenEndpoint, options.configuration.model, options.abortSignal);
64
+ throwIfAborted(options.abortSignal);
65
+ return await createSession(new options.WebSocketImplementation(require_session_config.createElevenLabsRealtimeUrl(options.realtimeUrl, token, options.configuration)), options.abortSignal, options.finishTimeoutMs);
66
+ }
67
+ async function requestToken(fetchImplementation, tokenEndpoint, model, abortSignal) {
68
+ let response;
69
+ try {
70
+ response = await fetchImplementation(tokenEndpoint, {
71
+ method: "POST",
72
+ headers: { "Content-Type": "application/json" },
73
+ body: JSON.stringify({ model }),
74
+ credentials: "same-origin",
75
+ signal: abortSignal
76
+ });
77
+ } catch (cause) {
78
+ throwIfAborted(abortSignal);
79
+ throw new _voiceinput_provider.VoiceInputError({
80
+ code: "network-error",
81
+ message: "Unable to reach the ElevenLabs token endpoint.",
82
+ provider: "elevenlabs",
83
+ retryable: true,
84
+ cause
85
+ });
86
+ }
87
+ if (!response.ok) throw await tokenResponseError(response);
88
+ let value;
89
+ try {
90
+ value = await response.json();
91
+ } catch (cause) {
92
+ throw new _voiceinput_provider.VoiceInputError({
93
+ code: "token-error",
94
+ message: "The ElevenLabs token endpoint returned invalid JSON.",
95
+ provider: "elevenlabs",
96
+ cause
97
+ });
98
+ }
99
+ if (!isRecord(value) || !nonEmpty(value["token"])) throw new _voiceinput_provider.VoiceInputError({
100
+ code: "token-error",
101
+ message: "The ElevenLabs token endpoint returned an invalid token.",
102
+ provider: "elevenlabs"
103
+ });
104
+ return value["token"];
105
+ }
106
+ async function createSession(socket, abortSignal, finishTimeoutMs) {
107
+ let controller;
108
+ const stream = new ReadableStream({ start(value) {
109
+ controller = value;
110
+ } });
111
+ if (controller === void 0) throw new _voiceinput_provider.VoiceInputError({
112
+ code: "provider-error",
113
+ message: "Unable to initialize the ElevenLabs transcript stream.",
114
+ provider: "elevenlabs"
115
+ });
116
+ let hasAudio = false;
117
+ let closed = false;
118
+ let failed = false;
119
+ let finishing = false;
120
+ let finishDeferred;
121
+ let finishSettled = false;
122
+ let finishTimer;
123
+ let finishDrainTimer;
124
+ let lastInterim = "";
125
+ let segmentSequence = 0;
126
+ const segmentId = () => `commit:${segmentSequence}`;
127
+ let speechActive = false;
128
+ const closeSocket = (reason) => {
129
+ if (socket.readyState === 0 || socket.readyState === 1) socket.close(1e3, reason);
130
+ };
131
+ const closeStream = () => {
132
+ if (closed) return;
133
+ closed = true;
134
+ abortSignal.removeEventListener("abort", abort);
135
+ controller?.close();
136
+ };
137
+ const settleFinish = (error) => {
138
+ if (finishDeferred === void 0 || finishSettled) return;
139
+ finishSettled = true;
140
+ if (finishTimer !== void 0) {
141
+ clearTimeout(finishTimer);
142
+ finishTimer = void 0;
143
+ }
144
+ if (finishDrainTimer !== void 0) {
145
+ clearTimeout(finishDrainTimer);
146
+ finishDrainTimer = void 0;
147
+ }
148
+ if (error === void 0) finishDeferred.resolve(void 0);
149
+ else finishDeferred.reject(error);
150
+ };
151
+ const finishCleanly = () => {
152
+ if (lastInterim.length > 0) {
153
+ lastInterim = "";
154
+ controller?.enqueue({
155
+ type: "interim",
156
+ text: "",
157
+ segmentId: segmentId()
158
+ });
159
+ }
160
+ endSpeech();
161
+ settleFinish();
162
+ closeStream();
163
+ closeSocket("finished");
164
+ };
165
+ const scheduleFinishDrain = () => {
166
+ if (!finishing || finishSettled) return;
167
+ if (finishDrainTimer !== void 0) clearTimeout(finishDrainTimer);
168
+ finishDrainTimer = setTimeout(finishCleanly, FINISH_DRAIN_MS);
169
+ };
170
+ const fail = (error) => {
171
+ if (closed || failed) return;
172
+ failed = true;
173
+ settleFinish(error);
174
+ controller?.enqueue({
175
+ type: "error",
176
+ error
177
+ });
178
+ closeStream();
179
+ closeSocket("aborted");
180
+ };
181
+ const startSpeech = () => {
182
+ if (!speechActive) {
183
+ speechActive = true;
184
+ controller?.enqueue({ type: "speech-start" });
185
+ }
186
+ };
187
+ const endSpeech = () => {
188
+ if (speechActive) {
189
+ speechActive = false;
190
+ controller?.enqueue({ type: "speech-end" });
191
+ }
192
+ };
193
+ const emitInterim = (text) => {
194
+ if (text.length === 0 || text === lastInterim) return;
195
+ startSpeech();
196
+ lastInterim = text;
197
+ controller?.enqueue({
198
+ type: "interim",
199
+ text,
200
+ segmentId: segmentId()
201
+ });
202
+ };
203
+ const commitTranscript = (text) => {
204
+ if (text.length > 0) startSpeech();
205
+ controller?.enqueue({
206
+ type: "final",
207
+ text,
208
+ segmentId: segmentId()
209
+ });
210
+ segmentSequence += 1;
211
+ lastInterim = "";
212
+ if (!finishing) endSpeech();
213
+ };
214
+ const handleMessage = (event) => {
215
+ if (closed) return;
216
+ try {
217
+ const value = JSON.parse(String(event.data));
218
+ if (!isRecord(value) || !nonEmpty(value["message_type"])) throw new TypeError("ElevenLabs sent an invalid Realtime event.");
219
+ const type = value["message_type"];
220
+ if (type === "partial_transcript") {
221
+ emitInterim(readText(value));
222
+ if (finishDrainTimer !== void 0) scheduleFinishDrain();
223
+ } else if (type === "final_transcript") {
224
+ if (finishDrainTimer !== void 0) scheduleFinishDrain();
225
+ } else if (type === "committed_transcript") {
226
+ commitTranscript(readText(value));
227
+ if (finishing) scheduleFinishDrain();
228
+ } else if (elevenLabsErrorTypes.has(type)) fail(normalizeRealtimeError(value));
229
+ } catch (cause) {
230
+ fail(new _voiceinput_provider.VoiceInputError({
231
+ code: "provider-error",
232
+ message: "ElevenLabs sent an invalid Realtime event.",
233
+ provider: "elevenlabs",
234
+ cause
235
+ }));
236
+ }
237
+ };
238
+ const handleClose = (event) => {
239
+ if (closed) return;
240
+ if (event.code === 1e3) {
241
+ if (finishing) {
242
+ finishCleanly();
243
+ return;
244
+ }
245
+ closeStream();
246
+ } else fail(new _voiceinput_provider.VoiceInputError({
247
+ code: "network-error",
248
+ message: "The ElevenLabs Realtime connection closed unexpectedly.",
249
+ provider: "elevenlabs",
250
+ retryable: true,
251
+ cause: event
252
+ }));
253
+ };
254
+ function abort() {
255
+ if (closed) return;
256
+ settleFinish(abortSignal.reason ?? new _voiceinput_provider.VoiceInputError({
257
+ code: "provider-error",
258
+ message: "The ElevenLabs Realtime session was aborted.",
259
+ provider: "elevenlabs"
260
+ }));
261
+ closeStream();
262
+ closeSocket("aborted");
263
+ }
264
+ socket.addEventListener("message", handleMessage);
265
+ socket.addEventListener("close", handleClose);
266
+ socket.addEventListener("error", () => {
267
+ fail(new _voiceinput_provider.VoiceInputError({
268
+ code: "network-error",
269
+ message: "The ElevenLabs Realtime connection failed.",
270
+ provider: "elevenlabs",
271
+ retryable: true
272
+ }));
273
+ });
274
+ abortSignal.addEventListener("abort", abort, { once: true });
275
+ await waitForOpen(socket, abortSignal);
276
+ return {
277
+ stream,
278
+ sendAudio(chunk) {
279
+ if (closed || finishing || chunk.length === 0) return;
280
+ hasAudio = true;
281
+ return (0, _voiceinput_provider_transport.sendWithBackpressure)(socket, Math.ceil(chunk.byteLength * 4 / 3) + 160, abortSignal, "elevenlabs", () => sendJson(socket, {
282
+ message_type: "input_audio_chunk",
283
+ audio_base_64: encodePcm16(chunk),
284
+ commit: false,
285
+ sample_rate: require_session_config.ELEVENLABS_SAMPLE_RATE
286
+ }));
287
+ },
288
+ finish() {
289
+ if (finishDeferred !== void 0) return finishDeferred.promise;
290
+ if (closed) return;
291
+ finishing = true;
292
+ finishDeferred = createDeferred();
293
+ if (!hasAudio) {
294
+ finishCleanly();
295
+ return finishDeferred.promise;
296
+ }
297
+ try {
298
+ sendJson(socket, {
299
+ message_type: "input_audio_chunk",
300
+ audio_base_64: "",
301
+ commit: true,
302
+ sample_rate: require_session_config.ELEVENLABS_SAMPLE_RATE
303
+ });
304
+ } catch (cause) {
305
+ fail(_voiceinput_provider.VoiceInputError.isInstance(cause) ? cause : new _voiceinput_provider.VoiceInputError({
306
+ code: "network-error",
307
+ message: "Unable to commit the ElevenLabs transcript.",
308
+ provider: "elevenlabs",
309
+ retryable: true,
310
+ cause
311
+ }));
312
+ return finishDeferred.promise;
313
+ }
314
+ finishTimer = setTimeout(() => {
315
+ fail(new _voiceinput_provider.VoiceInputError({
316
+ code: "network-error",
317
+ message: `ElevenLabs did not commit the final transcript within ${finishTimeoutMs}ms.`,
318
+ provider: "elevenlabs",
319
+ retryable: true
320
+ }));
321
+ }, finishTimeoutMs);
322
+ return finishDeferred.promise;
323
+ },
324
+ abort
325
+ };
326
+ }
327
+ const elevenLabsErrorTypes = /* @__PURE__ */ new Set([
328
+ "auth_error",
329
+ "quota_exceeded",
330
+ "transcriber_error",
331
+ "input_error",
332
+ "invalid_request",
333
+ "error",
334
+ "commit_throttled",
335
+ "unaccepted_terms",
336
+ "rate_limited",
337
+ "queue_overflow",
338
+ "resource_exhausted",
339
+ "session_time_limit_exceeded",
340
+ "chunk_size_exceeded",
341
+ "insufficient_audio_activity"
342
+ ]);
343
+ function normalizeRealtimeError(value) {
344
+ const type = String(value["message_type"]);
345
+ const rateLimited = type === "rate_limited" || type === "quota_exceeded" || type === "commit_throttled";
346
+ const retryable = rateLimited || type === "queue_overflow" || type === "resource_exhausted";
347
+ return new _voiceinput_provider.VoiceInputError({
348
+ code: type === "auth_error" ? "unauthorized" : rateLimited ? "rate-limited" : type === "input_error" || type === "chunk_size_exceeded" ? "audio-error" : "provider-error",
349
+ message: typeof value["error"] === "string" ? value["error"] : `ElevenLabs Realtime reported ${type}.`,
350
+ provider: "elevenlabs",
351
+ retryable,
352
+ cause: value
353
+ });
354
+ }
355
+ function invalidConfiguration(cause) {
356
+ return new _voiceinput_provider.VoiceInputError({
357
+ code: "invalid-configuration",
358
+ message: cause instanceof Error ? cause.message : "Invalid ElevenLabs transcription options.",
359
+ provider: "elevenlabs",
360
+ cause
361
+ });
362
+ }
363
+ function readText(value) {
364
+ if (typeof value["text"] !== "string") throw new TypeError("text must be a string.");
365
+ return value["text"];
366
+ }
367
+ function requireBrowserFunction(value, feature) {
368
+ if (typeof value !== "function") throw new _voiceinput_provider.VoiceInputError({
369
+ code: "unsupported-browser",
370
+ message: `ElevenLabs voice input requires browser ${feature} support.`,
371
+ provider: "elevenlabs"
372
+ });
373
+ }
374
+ function factoryString(value, name) {
375
+ if (value.trim().length === 0) throw invalidConfiguration(/* @__PURE__ */ new TypeError(`${name} must be non-empty.`));
376
+ return value;
377
+ }
378
+ function positiveInteger(value, name) {
379
+ if (!Number.isSafeInteger(value) || value <= 0) throw invalidConfiguration(/* @__PURE__ */ new TypeError(`${name} must be a positive safe integer.`));
380
+ return value;
381
+ }
382
+ function createDeferred() {
383
+ let resolve = () => {};
384
+ let reject = () => {};
385
+ return {
386
+ promise: new Promise((resolve_, reject_) => {
387
+ resolve = resolve_;
388
+ reject = reject_;
389
+ }),
390
+ resolve,
391
+ reject
392
+ };
393
+ }
394
+ async function tokenResponseError(response) {
395
+ const retryAfterMs = parseRetryAfter(response.headers.get("Retry-After"));
396
+ if (response.status === 401 || response.status === 403) return new _voiceinput_provider.VoiceInputError({
397
+ code: "unauthorized",
398
+ message: "The ElevenLabs token endpoint rejected this request.",
399
+ provider: "elevenlabs"
400
+ });
401
+ if (response.status === 429) return new _voiceinput_provider.VoiceInputError({
402
+ code: "rate-limited",
403
+ message: "The ElevenLabs token endpoint rate limit was exceeded.",
404
+ provider: "elevenlabs",
405
+ retryable: true,
406
+ ...retryAfterMs === void 0 ? {} : { retryAfterMs }
407
+ });
408
+ const safeError = await readSafeTokenError(response);
409
+ if (safeError !== void 0) return new _voiceinput_provider.VoiceInputError({
410
+ ...safeError,
411
+ provider: "elevenlabs"
412
+ });
413
+ return new _voiceinput_provider.VoiceInputError({
414
+ code: "token-error",
415
+ message: "The ElevenLabs token endpoint did not issue a token.",
416
+ provider: "elevenlabs",
417
+ retryable: response.status >= 500
418
+ });
419
+ }
420
+ async function readSafeTokenError(response) {
421
+ if (response.status !== 400 || response.headers.get("X-VoiceInput-Error") !== "1" || response.headers.get("Content-Type")?.split(";", 1)[0]?.trim() !== "application/json") return;
422
+ const text = await readBoundedErrorText(response);
423
+ if (text === void 0) return void 0;
424
+ try {
425
+ const value = JSON.parse(text);
426
+ const error = isRecord(value) ? value["error"] : void 0;
427
+ if (!isRecord(error) || error["code"] !== "invalid-configuration" && error["code"] !== "unsupported-feature" || typeof error["message"] !== "string" || error["message"].length === 0 || error["message"].length > 1e3) return;
428
+ return {
429
+ code: error["code"],
430
+ message: error["message"]
431
+ };
432
+ } catch {
433
+ return;
434
+ }
435
+ }
436
+ async function readBoundedErrorText(response) {
437
+ const reader = response.body?.getReader();
438
+ if (reader === void 0) return void 0;
439
+ const decoder = new TextDecoder();
440
+ let bytesRead = 0;
441
+ let text = "";
442
+ try {
443
+ while (true) {
444
+ const { done, value } = await reader.read();
445
+ if (done) return text + decoder.decode();
446
+ bytesRead += value.byteLength;
447
+ if (bytesRead > 4096) {
448
+ await reader.cancel().catch(() => {});
449
+ return;
450
+ }
451
+ text += decoder.decode(value, { stream: true });
452
+ }
453
+ } catch {
454
+ return;
455
+ }
456
+ }
457
+ function waitForOpen(socket, abortSignal) {
458
+ if (socket.readyState === 1) return Promise.resolve();
459
+ return new Promise((resolve, reject) => {
460
+ const cleanup = () => {
461
+ socket.removeEventListener("open", handleOpen);
462
+ socket.removeEventListener("error", handleError);
463
+ abortSignal.removeEventListener("abort", handleAbort);
464
+ };
465
+ const handleOpen = () => {
466
+ cleanup();
467
+ resolve();
468
+ };
469
+ const handleError = (event) => {
470
+ cleanup();
471
+ reject(new _voiceinput_provider.VoiceInputError({
472
+ code: "network-error",
473
+ message: "Unable to open the ElevenLabs Realtime connection.",
474
+ provider: "elevenlabs",
475
+ retryable: true,
476
+ cause: event
477
+ }));
478
+ };
479
+ const handleAbort = () => {
480
+ cleanup();
481
+ reject(abortSignal.reason);
482
+ };
483
+ socket.addEventListener("open", handleOpen, { once: true });
484
+ socket.addEventListener("error", handleError, { once: true });
485
+ abortSignal.addEventListener("abort", handleAbort, { once: true });
486
+ });
487
+ }
488
+ function sendJson(socket, value) {
489
+ try {
490
+ socket.send(JSON.stringify(value));
491
+ } catch (cause) {
492
+ throw new _voiceinput_provider.VoiceInputError({
493
+ code: "network-error",
494
+ message: "Unable to send data to ElevenLabs Realtime.",
495
+ provider: "elevenlabs",
496
+ retryable: true,
497
+ cause
498
+ });
499
+ }
500
+ }
501
+ function encodePcm16(chunk) {
502
+ const bytes = new Uint8Array(chunk.buffer, chunk.byteOffset, chunk.byteLength);
503
+ let binary = "";
504
+ for (let offset = 0; offset < bytes.length; offset += 8192) binary += String.fromCharCode(...bytes.subarray(offset, offset + 8192));
505
+ return btoa(binary);
506
+ }
507
+ function parseRetryAfter(value) {
508
+ if (value === null) return;
509
+ const seconds = Number(value);
510
+ if (Number.isFinite(seconds) && seconds >= 0) return Math.ceil(seconds * 1e3);
511
+ const date = Date.parse(value);
512
+ return Number.isNaN(date) ? void 0 : Math.max(0, date - Date.now());
513
+ }
514
+ function throwIfAborted(signal) {
515
+ if (signal.aborted) throw signal.reason;
516
+ }
517
+ function nonEmpty(value) {
518
+ return typeof value === "string" && value.length > 0;
519
+ }
520
+ function isRecord(value) {
521
+ return typeof value === "object" && value !== null && !Array.isArray(value);
522
+ }
523
+ //#endregion
524
+ exports.ELEVENLABS_DEFAULT_MODEL = require_session_config.ELEVENLABS_DEFAULT_MODEL;
525
+ exports.elevenlabs = elevenlabs;
526
+
527
+ //# sourceMappingURL=index.cjs.map