@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.
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 +11 -3
  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,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
+ }
@@ -0,0 +1,10 @@
1
+ export {
2
+ arrayBufferToBase64,
3
+ createElevenLabsClient,
4
+ dataUrlToBlob,
5
+ generateId,
6
+ getElevenLabsApiKeyFromEnv,
7
+ parseOutputFormat,
8
+ readStreamToArrayBuffer,
9
+ type ElevenLabsClientConfig,
10
+ } from './client'