@tanstack/ai-elevenlabs 0.2.34 → 0.2.36

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.
@@ -1,118 +1,139 @@
1
1
  import { ElevenLabsClient } from "@elevenlabs/elevenlabs-js";
2
- import { getApiKeyFromEnv, generateId as generateId$1 } from "@tanstack/ai-utils";
2
+ import { generateId, getApiKeyFromEnv } from "@tanstack/ai-utils";
3
+ //#region src/utils/client.ts
3
4
  function getEnvironment() {
4
- if (typeof globalThis !== "undefined") {
5
- const win = globalThis.window;
6
- if (win?.env) return win.env;
7
- }
8
- if (typeof process !== "undefined") {
9
- return process.env;
10
- }
11
- return void 0;
5
+ if (typeof globalThis !== "undefined") {
6
+ const win = globalThis.window;
7
+ if (win?.env) return win.env;
8
+ }
9
+ if (typeof process !== "undefined") return process.env;
12
10
  }
13
11
  function getElevenLabsApiKeyFromEnv() {
14
- return getApiKeyFromEnv("ELEVENLABS_API_KEY");
12
+ return getApiKeyFromEnv("ELEVENLABS_API_KEY");
15
13
  }
16
14
  function getElevenLabsAgentIdFromEnv() {
17
- const id = getEnvironment()?.ELEVENLABS_AGENT_ID;
18
- if (!id) {
19
- throw new Error(
20
- "ELEVENLABS_AGENT_ID is required. Please set it in your environment variables or pass `agentId` explicitly to elevenlabsRealtimeToken()."
21
- );
22
- }
23
- return id;
15
+ const id = getEnvironment()?.ELEVENLABS_AGENT_ID;
16
+ if (!id) throw new Error("ELEVENLABS_AGENT_ID is required. Please set it in your environment variables or pass `agentId` explicitly to elevenlabsRealtimeToken().");
17
+ return id;
24
18
  }
19
+ /**
20
+ * Build an `ElevenLabsClient` with env-based or explicit credentials.
21
+ * Each adapter calls this once at construction time so unit tests can
22
+ * pass in an explicit key without needing `process.env`.
23
+ */
25
24
  function createElevenLabsClient(config) {
26
- const apiKey = config?.apiKey ?? getElevenLabsApiKeyFromEnv();
27
- return new ElevenLabsClient({
28
- apiKey,
29
- ...config?.baseUrl ? { baseUrl: config.baseUrl } : {},
30
- ...config?.timeoutInSeconds != null ? { timeoutInSeconds: config.timeoutInSeconds } : {},
31
- ...config?.maxRetries != null ? { maxRetries: config.maxRetries } : {},
32
- ...config?.headers ? { headers: config.headers } : {}
33
- });
25
+ return new ElevenLabsClient({
26
+ apiKey: config?.apiKey ?? getElevenLabsApiKeyFromEnv(),
27
+ ...config?.baseUrl ? { baseUrl: config.baseUrl } : {},
28
+ ...config?.timeoutInSeconds != null ? { timeoutInSeconds: config.timeoutInSeconds } : {},
29
+ ...config?.maxRetries != null ? { maxRetries: config.maxRetries } : {},
30
+ ...config?.headers ? { headers: config.headers } : {}
31
+ });
34
32
  }
35
- const generateId = generateId$1;
33
+ var generateId$1 = generateId;
34
+ /**
35
+ * Convert an ArrayBuffer to base64 in a cross-runtime way.
36
+ *
37
+ * The naive `btoa(String.fromCharCode(...bytes))` form blows up V8's argument
38
+ * limit (~65k) on realistic audio payloads, so we either use `Buffer`
39
+ * (Node / Bun) or walk the byte array in a single loop (browser).
40
+ */
36
41
  function arrayBufferToBase64(buffer) {
37
- if (typeof Buffer !== "undefined" && typeof Buffer.from === "function") {
38
- return Buffer.from(buffer).toString("base64");
39
- }
40
- const view = new Uint8Array(buffer);
41
- let binary = "";
42
- for (const byte of view) {
43
- binary += String.fromCharCode(byte);
44
- }
45
- return btoa(binary);
42
+ if (typeof Buffer !== "undefined" && typeof Buffer.from === "function") return Buffer.from(buffer).toString("base64");
43
+ const view = new Uint8Array(buffer);
44
+ let binary = "";
45
+ for (const byte of view) binary += String.fromCharCode(byte);
46
+ return btoa(binary);
46
47
  }
48
+ /**
49
+ * Drain a `ReadableStream<Uint8Array>` (what the ElevenLabs SDK returns for
50
+ * audio endpoints) into a single `Uint8Array`, then expose it as an
51
+ * `ArrayBuffer` slice. We concatenate ourselves rather than going through
52
+ * `new Response(stream).arrayBuffer()` so we stay runtime-agnostic.
53
+ */
47
54
  async function readStreamToArrayBuffer(stream) {
48
- const reader = stream.getReader();
49
- const chunks = [];
50
- let total = 0;
51
- try {
52
- let result = await reader.read();
53
- while (!result.done) {
54
- chunks.push(result.value);
55
- total += result.value.byteLength;
56
- result = await reader.read();
57
- }
58
- } finally {
59
- reader.releaseLock();
60
- }
61
- const merged = new Uint8Array(total);
62
- let offset = 0;
63
- for (const chunk of chunks) {
64
- merged.set(chunk, offset);
65
- offset += chunk.byteLength;
66
- }
67
- return merged.buffer.slice(
68
- merged.byteOffset,
69
- merged.byteOffset + merged.byteLength
70
- );
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(merged.byteOffset, merged.byteOffset + merged.byteLength);
71
75
  }
76
+ /**
77
+ * Decode a `data:` URL into a Blob for upload to the ElevenLabs STT API
78
+ * (which only accepts multipart files or https URLs). Supports base64 and
79
+ * URL-encoded payloads. Returns `undefined` for non-data-URL strings so the
80
+ * caller can fall through to treating the input as an https URL.
81
+ */
72
82
  function dataUrlToBlob(value) {
73
- if (!value.startsWith("data:")) return void 0;
74
- const commaIndex = value.indexOf(",");
75
- if (commaIndex === -1) return void 0;
76
- const header = value.slice(5, commaIndex);
77
- const payload = value.slice(commaIndex + 1);
78
- const isBase64 = /;base64$/i.test(header);
79
- const mimeType = header.split(";")[0] || "application/octet-stream";
80
- if (isBase64) {
81
- const binary = atob(payload);
82
- const bytes = new Uint8Array(binary.length);
83
- for (let i = 0; i < binary.length; i += 1) {
84
- bytes[i] = binary.charCodeAt(i);
85
- }
86
- return new Blob([bytes], { type: mimeType });
87
- }
88
- return new Blob([decodeURIComponent(payload)], { type: mimeType });
83
+ if (!value.startsWith("data:")) return void 0;
84
+ const commaIndex = value.indexOf(",");
85
+ if (commaIndex === -1) return void 0;
86
+ const header = value.slice(5, commaIndex);
87
+ const payload = value.slice(commaIndex + 1);
88
+ const isBase64 = /;base64$/i.test(header);
89
+ const mimeType = header.split(";")[0] || "application/octet-stream";
90
+ if (isBase64) {
91
+ const binary = atob(payload);
92
+ const bytes = new Uint8Array(binary.length);
93
+ for (let i = 0; i < binary.length; i += 1) bytes[i] = binary.charCodeAt(i);
94
+ return new Blob([bytes], { type: mimeType });
95
+ }
96
+ return new Blob([decodeURIComponent(payload)], { type: mimeType });
89
97
  }
98
+ /**
99
+ * Break an ElevenLabs `output_format` string (`mp3_44100_128`,
100
+ * `pcm_24000`, `opus_48000_64`, `ulaw_8000`, ...) into a file extension and
101
+ * content-type suitable for `TTSResult` / `AudioGenerationResult` consumers.
102
+ *
103
+ * Unknown codecs fall back to `mp3` / `audio/mpeg` because the ElevenLabs
104
+ * default is `mp3_44100_128` — mispredicting on an exotic format is safer
105
+ * than throwing in the adapter.
106
+ */
90
107
  function parseOutputFormat(fmt) {
91
- const codec = (fmt || "mp3_44100_128").split("_")[0]?.toLowerCase();
92
- switch (codec) {
93
- case "mp3":
94
- return { format: "mp3", contentType: "audio/mpeg" };
95
- case "pcm":
96
- return { format: "pcm", contentType: "audio/pcm" };
97
- case "opus":
98
- return { format: "opus", contentType: "audio/opus" };
99
- case "ulaw":
100
- return { format: "ulaw", contentType: "audio/basic" };
101
- case "alaw":
102
- return { format: "alaw", contentType: "audio/x-alaw-basic" };
103
- case void 0:
104
- default:
105
- return { format: "mp3", contentType: "audio/mpeg" };
106
- }
108
+ switch ((fmt || "mp3_44100_128").split("_")[0]?.toLowerCase()) {
109
+ case "mp3": return {
110
+ format: "mp3",
111
+ contentType: "audio/mpeg"
112
+ };
113
+ case "pcm": return {
114
+ format: "pcm",
115
+ contentType: "audio/pcm"
116
+ };
117
+ case "opus": return {
118
+ format: "opus",
119
+ contentType: "audio/opus"
120
+ };
121
+ case "ulaw": return {
122
+ format: "ulaw",
123
+ contentType: "audio/basic"
124
+ };
125
+ case "alaw": return {
126
+ format: "alaw",
127
+ contentType: "audio/x-alaw-basic"
128
+ };
129
+ case void 0:
130
+ default: return {
131
+ format: "mp3",
132
+ contentType: "audio/mpeg"
133
+ };
134
+ }
107
135
  }
108
- export {
109
- arrayBufferToBase64,
110
- createElevenLabsClient,
111
- dataUrlToBlob,
112
- generateId,
113
- getElevenLabsAgentIdFromEnv,
114
- getElevenLabsApiKeyFromEnv,
115
- parseOutputFormat,
116
- readStreamToArrayBuffer
117
- };
118
- //# sourceMappingURL=client.js.map
136
+ //#endregion
137
+ export { arrayBufferToBase64, createElevenLabsClient, dataUrlToBlob, generateId$1 as generateId, getElevenLabsAgentIdFromEnv, getElevenLabsApiKeyFromEnv, parseOutputFormat, readStreamToArrayBuffer };
138
+
139
+ //# sourceMappingURL=client.js.map
@@ -1 +1 @@
1
- {"version":3,"file":"client.js","sources":["../../../src/utils/client.ts"],"sourcesContent":["import { ElevenLabsClient } from '@elevenlabs/elevenlabs-js'\nimport {\n getApiKeyFromEnv,\n generateId as sharedGenerateId,\n} from '@tanstack/ai-utils'\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 return getApiKeyFromEnv('ELEVENLABS_API_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\n// Re-exported from `@tanstack/ai-utils` so existing callers keep working\n// while the implementation stays deduped across provider packages.\nexport const generateId = sharedGenerateId\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 (const byte of view) {\n binary += String.fromCharCode(byte)\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 case undefined:\n default:\n // `split('_')[0]?.toLowerCase()` can be `undefined` only if `fmt` is\n // an empty string; any other unknown codec also falls back to mp3.\n return { format: 'mp3', contentType: 'audio/mpeg' }\n }\n}\n"],"names":["sharedGenerateId"],"mappings":";;AAiCA,SAAS,iBAAwC;AAC/C,MAAI,OAAO,eAAe,aAAa;AACrC,UAAM,MAAO,WAA0C;AACvD,QAAI,KAAK,IAAK,QAAO,IAAI;AAAA,EAC3B;AACA,MAAI,OAAO,YAAY,aAAa;AAClC,WAAO,QAAQ;AAAA,EACjB;AACA,SAAO;AACT;AAEO,SAAS,6BAAqC;AACnD,SAAO,iBAAiB,oBAAoB;AAC9C;AAEO,SAAS,8BAAsC;AACpD,QAAM,KAAK,kBAAkB;AAC7B,MAAI,CAAC,IAAI;AACP,UAAM,IAAI;AAAA,MACR;AAAA,IAAA;AAAA,EAGJ;AACA,SAAO;AACT;AAOO,SAAS,uBACd,QACkB;AAClB,QAAM,SAAS,QAAQ,UAAU,2BAAA;AACjC,SAAO,IAAI,iBAAiB;AAAA,IAC1B;AAAA,IACA,GAAI,QAAQ,UAAU,EAAE,SAAS,OAAO,QAAA,IAAY,CAAA;AAAA,IACpD,GAAI,QAAQ,oBAAoB,OAC5B,EAAE,kBAAkB,OAAO,iBAAA,IAC3B,CAAA;AAAA,IACJ,GAAI,QAAQ,cAAc,OAAO,EAAE,YAAY,OAAO,WAAA,IAAe,CAAA;AAAA,IACrE,GAAI,QAAQ,UAAU,EAAE,SAAS,OAAO,QAAA,IAAY,CAAA;AAAA,EAAC,CACtD;AACH;AAIO,MAAM,aAAaA;AASnB,SAAS,oBAAoB,QAA6B;AAC/D,MAAI,OAAO,WAAW,eAAe,OAAO,OAAO,SAAS,YAAY;AACtE,WAAO,OAAO,KAAK,MAAM,EAAE,SAAS,QAAQ;AAAA,EAC9C;AACA,QAAM,OAAO,IAAI,WAAW,MAAM;AAClC,MAAI,SAAS;AACb,aAAW,QAAQ,MAAM;AACvB,cAAU,OAAO,aAAa,IAAI;AAAA,EACpC;AACA,SAAO,KAAK,MAAM;AACpB;AAQA,eAAsB,wBACpB,QACsB;AACtB,QAAM,SAAS,OAAO,UAAA;AACtB,QAAM,SAA4B,CAAA;AAClC,MAAI,QAAQ;AACZ,MAAI;AACF,QAAI,SAAS,MAAM,OAAO,KAAA;AAC1B,WAAO,CAAC,OAAO,MAAM;AACnB,aAAO,KAAK,OAAO,KAAK;AACxB,eAAS,OAAO,MAAM;AACtB,eAAS,MAAM,OAAO,KAAA;AAAA,IACxB;AAAA,EACF,UAAA;AACE,WAAO,YAAA;AAAA,EACT;AACA,QAAM,SAAS,IAAI,WAAW,KAAK;AACnC,MAAI,SAAS;AACb,aAAW,SAAS,QAAQ;AAC1B,WAAO,IAAI,OAAO,MAAM;AACxB,cAAU,MAAM;AAAA,EAClB;AACA,SAAO,OAAO,OAAO;AAAA,IACnB,OAAO;AAAA,IACP,OAAO,aAAa,OAAO;AAAA,EAAA;AAE/B;AAQO,SAAS,cAAc,OAAiC;AAC7D,MAAI,CAAC,MAAM,WAAW,OAAO,EAAG,QAAO;AACvC,QAAM,aAAa,MAAM,QAAQ,GAAG;AACpC,MAAI,eAAe,GAAI,QAAO;AAE9B,QAAM,SAAS,MAAM,MAAM,GAAG,UAAU;AACxC,QAAM,UAAU,MAAM,MAAM,aAAa,CAAC;AAC1C,QAAM,WAAW,YAAY,KAAK,MAAM;AACxC,QAAM,WAAW,OAAO,MAAM,GAAG,EAAE,CAAC,KAAK;AAEzC,MAAI,UAAU;AACZ,UAAM,SAAS,KAAK,OAAO;AAC3B,UAAM,QAAQ,IAAI,WAAW,OAAO,MAAM;AAC1C,aAAS,IAAI,GAAG,IAAI,OAAO,QAAQ,KAAK,GAAG;AACzC,YAAM,CAAC,IAAI,OAAO,WAAW,CAAC;AAAA,IAChC;AACA,WAAO,IAAI,KAAK,CAAC,KAAK,GAAG,EAAE,MAAM,UAAU;AAAA,EAC7C;AAEA,SAAO,IAAI,KAAK,CAAC,mBAAmB,OAAO,CAAC,GAAG,EAAE,MAAM,UAAU;AACnE;AAWO,SAAS,kBAAkB,KAGhC;AACA,QAAM,SAAS,OAAO,iBAAiB,MAAM,GAAG,EAAE,CAAC,GAAG,YAAA;AACtD,UAAQ,OAAA;AAAA,IACN,KAAK;AACH,aAAO,EAAE,QAAQ,OAAO,aAAa,aAAA;AAAA,IACvC,KAAK;AACH,aAAO,EAAE,QAAQ,OAAO,aAAa,YAAA;AAAA,IACvC,KAAK;AACH,aAAO,EAAE,QAAQ,QAAQ,aAAa,aAAA;AAAA,IACxC,KAAK;AACH,aAAO,EAAE,QAAQ,QAAQ,aAAa,cAAA;AAAA,IACxC,KAAK;AACH,aAAO,EAAE,QAAQ,QAAQ,aAAa,qBAAA;AAAA,IACxC,KAAK;AAAA,IACL;AAGE,aAAO,EAAE,QAAQ,OAAO,aAAa,aAAA;AAAA,EAAa;AAExD;"}
1
+ {"version":3,"file":"client.js","names":[],"sources":["../../../src/utils/client.ts"],"sourcesContent":["import { ElevenLabsClient } from '@elevenlabs/elevenlabs-js'\nimport {\n getApiKeyFromEnv,\n generateId as sharedGenerateId,\n} from '@tanstack/ai-utils'\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 return getApiKeyFromEnv('ELEVENLABS_API_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\n// Re-exported from `@tanstack/ai-utils` so existing callers keep working\n// while the implementation stays deduped across provider packages.\nexport const generateId = sharedGenerateId\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 (const byte of view) {\n binary += String.fromCharCode(byte)\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 case undefined:\n default:\n // `split('_')[0]?.toLowerCase()` can be `undefined` only if `fmt` is\n // an empty string; any other unknown codec also falls back to mp3.\n return { format: 'mp3', contentType: 'audio/mpeg' }\n }\n}\n"],"mappings":";;;AAiCA,SAAS,iBAAwC;CAC/C,IAAI,OAAO,eAAe,aAAa;EACrC,MAAM,MAAO,WAA0C;EACvD,IAAI,KAAK,KAAK,OAAO,IAAI;CAC3B;CACA,IAAI,OAAO,YAAY,aACrB,OAAO,QAAQ;AAGnB;AAEA,SAAgB,6BAAqC;CACnD,OAAO,iBAAiB,oBAAoB;AAC9C;AAEA,SAAgB,8BAAsC;CACpD,MAAM,KAAK,eAAe,CAAC,EAAE;CAC7B,IAAI,CAAC,IACH,MAAM,IAAI,MACR,yIAEF;CAEF,OAAO;AACT;;;;;;AAOA,SAAgB,uBACd,QACkB;CAElB,OAAO,IAAI,iBAAiB;EAC1B,QAFa,QAAQ,UAAU,2BAA2B;EAG1D,GAAI,QAAQ,UAAU,EAAE,SAAS,OAAO,QAAQ,IAAI,CAAC;EACrD,GAAI,QAAQ,oBAAoB,OAC5B,EAAE,kBAAkB,OAAO,iBAAiB,IAC5C,CAAC;EACL,GAAI,QAAQ,cAAc,OAAO,EAAE,YAAY,OAAO,WAAW,IAAI,CAAC;EACtE,GAAI,QAAQ,UAAU,EAAE,SAAS,OAAO,QAAQ,IAAI,CAAC;CACvD,CAAC;AACH;AAIA,IAAa,eAAa;;;;;;;;AAS1B,SAAgB,oBAAoB,QAA6B;CAC/D,IAAI,OAAO,WAAW,eAAe,OAAO,OAAO,SAAS,YAC1D,OAAO,OAAO,KAAK,MAAM,CAAC,CAAC,SAAS,QAAQ;CAE9C,MAAM,OAAO,IAAI,WAAW,MAAM;CAClC,IAAI,SAAS;CACb,KAAK,MAAM,QAAQ,MACjB,UAAU,OAAO,aAAa,IAAI;CAEpC,OAAO,KAAK,MAAM;AACpB;;;;;;;AAQA,eAAsB,wBACpB,QACsB;CACtB,MAAM,SAAS,OAAO,UAAU;CAChC,MAAM,SAA4B,CAAC;CACnC,IAAI,QAAQ;CACZ,IAAI;EACF,IAAI,SAAS,MAAM,OAAO,KAAK;EAC/B,OAAO,CAAC,OAAO,MAAM;GACnB,OAAO,KAAK,OAAO,KAAK;GACxB,SAAS,OAAO,MAAM;GACtB,SAAS,MAAM,OAAO,KAAK;EAC7B;CACF,UAAU;EACR,OAAO,YAAY;CACrB;CACA,MAAM,SAAS,IAAI,WAAW,KAAK;CACnC,IAAI,SAAS;CACb,KAAK,MAAM,SAAS,QAAQ;EAC1B,OAAO,IAAI,OAAO,MAAM;EACxB,UAAU,MAAM;CAClB;CACA,OAAO,OAAO,OAAO,MACnB,OAAO,YACP,OAAO,aAAa,OAAO,UAC7B;AACF;;;;;;;AAQA,SAAgB,cAAc,OAAiC;CAC7D,IAAI,CAAC,MAAM,WAAW,OAAO,GAAG,OAAO,KAAA;CACvC,MAAM,aAAa,MAAM,QAAQ,GAAG;CACpC,IAAI,eAAe,IAAI,OAAO,KAAA;CAE9B,MAAM,SAAS,MAAM,MAAM,GAAG,UAAU;CACxC,MAAM,UAAU,MAAM,MAAM,aAAa,CAAC;CAC1C,MAAM,WAAW,YAAY,KAAK,MAAM;CACxC,MAAM,WAAW,OAAO,MAAM,GAAG,CAAC,CAAC,MAAM;CAEzC,IAAI,UAAU;EACZ,MAAM,SAAS,KAAK,OAAO;EAC3B,MAAM,QAAQ,IAAI,WAAW,OAAO,MAAM;EAC1C,KAAK,IAAI,IAAI,GAAG,IAAI,OAAO,QAAQ,KAAK,GACtC,MAAM,KAAK,OAAO,WAAW,CAAC;EAEhC,OAAO,IAAI,KAAK,CAAC,KAAK,GAAG,EAAE,MAAM,SAAS,CAAC;CAC7C;CAEA,OAAO,IAAI,KAAK,CAAC,mBAAmB,OAAO,CAAC,GAAG,EAAE,MAAM,SAAS,CAAC;AACnE;;;;;;;;;;AAWA,SAAgB,kBAAkB,KAGhC;CAEA,SADe,OAAO,gBAAA,CAAiB,MAAM,GAAG,CAAC,CAAC,EAAE,EAAE,YAAY,GAClE;EACE,KAAK,OACH,OAAO;GAAE,QAAQ;GAAO,aAAa;EAAa;EACpD,KAAK,OACH,OAAO;GAAE,QAAQ;GAAO,aAAa;EAAY;EACnD,KAAK,QACH,OAAO;GAAE,QAAQ;GAAQ,aAAa;EAAa;EACrD,KAAK,QACH,OAAO;GAAE,QAAQ;GAAQ,aAAa;EAAc;EACtD,KAAK,QACH,OAAO;GAAE,QAAQ;GAAQ,aAAa;EAAqB;EAC7D,KAAK,KAAA;EACL,SAGE,OAAO;GAAE,QAAQ;GAAO,aAAa;EAAa;CACtD;AACF"}
@@ -0,0 +1,2 @@
1
+ import { getElevenLabsApiKeyFromEnv } from "./client.js";
2
+ export { getElevenLabsApiKeyFromEnv };
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@tanstack/ai-elevenlabs",
3
- "version": "0.2.34",
3
+ "version": "0.2.36",
4
4
  "description": "ElevenLabs adapter for TanStack AI realtime voice, text-to-speech, transcription, music, and sound effects.",
5
5
  "author": "Tanner Linsley",
6
6
  "license": "MIT",
@@ -50,21 +50,21 @@
50
50
  "dependencies": {
51
51
  "@elevenlabs/client": "^1.3.1",
52
52
  "@elevenlabs/elevenlabs-js": "^2.44.0",
53
- "@tanstack/ai-utils": "0.3.1"
53
+ "@tanstack/ai-utils": "^0.4.0"
54
54
  },
55
55
  "peerDependencies": {
56
- "@tanstack/ai": "^0.42.0"
56
+ "@tanstack/ai": "^0.44.0"
57
57
  },
58
58
  "devDependencies": {
59
59
  "@vitest/coverage-v8": "4.0.14",
60
- "@tanstack/ai": "0.42.0"
60
+ "@tanstack/ai": "0.44.0"
61
61
  },
62
62
  "scripts": {
63
63
  "build": "vite build",
64
64
  "clean": "premove ./build ./dist",
65
- "lint:fix": "eslint ./src --fix",
65
+ "lint:fix": "oxlint src --type-aware --fix",
66
66
  "test:build": "publint --strict",
67
- "test:eslint": "eslint ./src",
67
+ "test:oxlint": "oxlint src --type-aware",
68
68
  "test:lib": "vitest --passWithNoTests",
69
69
  "test:lib:dev": "pnpm test:lib --watch",
70
70
  "test:types": "tsc"
@@ -165,7 +165,7 @@ export class ElevenLabsTranscriptionAdapter<
165
165
  // The SDK types this as a union of single- and multi-channel responses.
166
166
  // We treat multi-channel as "join the channel transcripts" — consumers
167
167
  // who care about per-channel detail can re-parse from `modelOptions`.
168
- // eslint-disable-next-line no-restricted-syntax -- bridges SpeechToTextConvertResponse union (incl. webhook variant with no text/words/transcripts) to a flattened duck-typed shape we discriminate at runtime
168
+ // oxlint-disable-next-line eslint-js/no-restricted-syntax -- bridges SpeechToTextConvertResponse union (incl. webhook variant with no text/words/transcripts) to a flattened duck-typed shape we discriminate at runtime
169
169
  const data = response as unknown as {
170
170
  text?: string
171
171
  languageCode?: string
@@ -1 +0,0 @@
1
- {"version":3,"file":"index.js","sources":[],"sourcesContent":[],"names":[],"mappings":";;;;;;;"}