@voxagent/bridge 0.1.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/README.md +74 -0
- package/dist/audio/source.d.ts +23 -0
- package/dist/audio/source.d.ts.map +1 -0
- package/dist/audio/source.js +69 -0
- package/dist/audio/source.js.map +1 -0
- package/dist/cli.d.ts +13 -0
- package/dist/cli.d.ts.map +1 -0
- package/dist/cli.js +171 -0
- package/dist/cli.js.map +1 -0
- package/dist/config.d.ts +62 -0
- package/dist/config.d.ts.map +1 -0
- package/dist/config.js +102 -0
- package/dist/config.js.map +1 -0
- package/dist/drivers/claude.d.ts +36 -0
- package/dist/drivers/claude.d.ts.map +1 -0
- package/dist/drivers/claude.js +445 -0
- package/dist/drivers/claude.js.map +1 -0
- package/dist/drivers/codex.d.ts +56 -0
- package/dist/drivers/codex.d.ts.map +1 -0
- package/dist/drivers/codex.js +652 -0
- package/dist/drivers/codex.js.map +1 -0
- package/dist/drivers/index.d.ts +34 -0
- package/dist/drivers/index.d.ts.map +1 -0
- package/dist/drivers/index.js +18 -0
- package/dist/drivers/index.js.map +1 -0
- package/dist/drivers/types.d.ts +81 -0
- package/dist/drivers/types.d.ts.map +1 -0
- package/dist/drivers/types.js +11 -0
- package/dist/drivers/types.js.map +1 -0
- package/dist/index.d.ts +6 -0
- package/dist/index.d.ts.map +1 -0
- package/dist/index.js +5 -0
- package/dist/index.js.map +1 -0
- package/dist/logging.d.ts +15 -0
- package/dist/logging.d.ts.map +1 -0
- package/dist/logging.js +36 -0
- package/dist/logging.js.map +1 -0
- package/dist/pair.d.ts +25 -0
- package/dist/pair.d.ts.map +1 -0
- package/dist/pair.js +41 -0
- package/dist/pair.js.map +1 -0
- package/dist/permissions.d.ts +67 -0
- package/dist/permissions.d.ts.map +1 -0
- package/dist/permissions.js +221 -0
- package/dist/permissions.js.map +1 -0
- package/dist/protocol.d.ts +181 -0
- package/dist/protocol.d.ts.map +1 -0
- package/dist/protocol.js +63 -0
- package/dist/protocol.js.map +1 -0
- package/dist/room.d.ts +52 -0
- package/dist/room.d.ts.map +1 -0
- package/dist/room.js +401 -0
- package/dist/room.js.map +1 -0
- package/dist/tts/elevenlabs.d.ts +43 -0
- package/dist/tts/elevenlabs.d.ts.map +1 -0
- package/dist/tts/elevenlabs.js +268 -0
- package/dist/tts/elevenlabs.js.map +1 -0
- package/dist/tts/index.d.ts +22 -0
- package/dist/tts/index.d.ts.map +1 -0
- package/dist/tts/index.js +23 -0
- package/dist/tts/index.js.map +1 -0
- package/dist/tts/openai.d.ts +35 -0
- package/dist/tts/openai.d.ts.map +1 -0
- package/dist/tts/openai.js +270 -0
- package/dist/tts/openai.js.map +1 -0
- package/dist/tts/types.d.ts +28 -0
- package/dist/tts/types.d.ts.map +1 -0
- package/dist/tts/types.js +5 -0
- package/dist/tts/types.js.map +1 -0
- package/package.json +68 -0
|
@@ -0,0 +1,268 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* @file ElevenLabs streaming TTS via WebSocket.
|
|
3
|
+
*
|
|
4
|
+
* Requests raw PCM at a configurable sample rate so we can push samples
|
|
5
|
+
* directly into LiveKit's AudioSource without an MP3 decode step. The
|
|
6
|
+
* default is `pcm_16000` (Starter tier and above); higher rates require
|
|
7
|
+
* Creator/Pro tiers — pass `audioFormat` to opt up.
|
|
8
|
+
*
|
|
9
|
+
* Tier reference (as of 2026-04):
|
|
10
|
+
* pcm_16000 — Starter+
|
|
11
|
+
* pcm_22050 — Creator+
|
|
12
|
+
* pcm_24000 — Pro+
|
|
13
|
+
* pcm_44100 — Pro+
|
|
14
|
+
*/
|
|
15
|
+
import { WebSocket as UndiciWebSocket } from 'undici';
|
|
16
|
+
import { log } from '../logging.js';
|
|
17
|
+
const ELEVENLABS_WS_BASE = 'wss://api.elevenlabs.io/v1/text-to-speech';
|
|
18
|
+
const NUM_CHANNELS = 1;
|
|
19
|
+
const FORMAT_TO_SAMPLE_RATE = {
|
|
20
|
+
pcm_16000: 16_000,
|
|
21
|
+
pcm_22050: 22_050,
|
|
22
|
+
pcm_24000: 24_000,
|
|
23
|
+
pcm_44100: 44_100,
|
|
24
|
+
};
|
|
25
|
+
/** 20 ms framing — LiveKit handles any sample rate the AudioSource is built with. */
|
|
26
|
+
function framesFor(sampleRate) {
|
|
27
|
+
return Math.round(sampleRate * 0.02);
|
|
28
|
+
}
|
|
29
|
+
export class ElevenLabsEngine {
|
|
30
|
+
options;
|
|
31
|
+
sampleRate;
|
|
32
|
+
numChannels = NUM_CHANNELS;
|
|
33
|
+
audioFormat;
|
|
34
|
+
frameSamples;
|
|
35
|
+
constructor(options) {
|
|
36
|
+
this.options = options;
|
|
37
|
+
this.audioFormat = options.audioFormat ?? 'pcm_16000';
|
|
38
|
+
this.sampleRate = FORMAT_TO_SAMPLE_RATE[this.audioFormat];
|
|
39
|
+
this.frameSamples = framesFor(this.sampleRate);
|
|
40
|
+
}
|
|
41
|
+
beginUtterance(utteranceId) {
|
|
42
|
+
return new ElevenLabsUtterance(utteranceId, this.options, this.audioFormat, this.frameSamples);
|
|
43
|
+
}
|
|
44
|
+
async close() {
|
|
45
|
+
// No persistent resources at the engine level.
|
|
46
|
+
}
|
|
47
|
+
}
|
|
48
|
+
class ElevenLabsUtterance {
|
|
49
|
+
utteranceId;
|
|
50
|
+
options;
|
|
51
|
+
audioFormat;
|
|
52
|
+
frameSamples;
|
|
53
|
+
ws = null;
|
|
54
|
+
opened = false;
|
|
55
|
+
finished = false;
|
|
56
|
+
cancelled = false;
|
|
57
|
+
leftoverBytes = Buffer.alloc(0);
|
|
58
|
+
// Frame queue with async iterator backpressure.
|
|
59
|
+
frameQueue = [];
|
|
60
|
+
frameWaiters = [];
|
|
61
|
+
endOfStream = false;
|
|
62
|
+
// Text input queue used until the WS handshake completes.
|
|
63
|
+
pendingText = [];
|
|
64
|
+
constructor(utteranceId, options, audioFormat, frameSamples) {
|
|
65
|
+
this.utteranceId = utteranceId;
|
|
66
|
+
this.options = options;
|
|
67
|
+
this.audioFormat = audioFormat;
|
|
68
|
+
this.frameSamples = frameSamples;
|
|
69
|
+
void this.openWebSocket();
|
|
70
|
+
}
|
|
71
|
+
async openWebSocket() {
|
|
72
|
+
const url = `${ELEVENLABS_WS_BASE}/${encodeURIComponent(this.options.voiceId)}/stream-input?model_id=${encodeURIComponent(this.options.modelId)}&output_format=${this.audioFormat}`;
|
|
73
|
+
const ws = new UndiciWebSocket(url, {
|
|
74
|
+
headers: {
|
|
75
|
+
'xi-api-key': this.options.apiKey,
|
|
76
|
+
},
|
|
77
|
+
});
|
|
78
|
+
this.ws = ws;
|
|
79
|
+
ws.addEventListener('open', () => {
|
|
80
|
+
if (this.cancelled) {
|
|
81
|
+
ws.close();
|
|
82
|
+
return;
|
|
83
|
+
}
|
|
84
|
+
this.opened = true;
|
|
85
|
+
// ElevenLabs requires a JSON config message before any text.
|
|
86
|
+
ws.send(JSON.stringify({
|
|
87
|
+
text: ' ',
|
|
88
|
+
voice_settings: {
|
|
89
|
+
stability: this.options.voiceSettings?.stability ?? 0.42,
|
|
90
|
+
similarity_boost: this.options.voiceSettings?.similarityBoost ?? 0.85,
|
|
91
|
+
style: this.options.voiceSettings?.style ?? 0.6,
|
|
92
|
+
use_speaker_boost: this.options.voiceSettings?.useSpeakerBoost ?? true,
|
|
93
|
+
},
|
|
94
|
+
generation_config: { chunk_length_schedule: [120, 160, 250, 290] },
|
|
95
|
+
}));
|
|
96
|
+
// Flush any text buffered before open.
|
|
97
|
+
for (const text of this.pendingText) {
|
|
98
|
+
ws.send(JSON.stringify({ text }));
|
|
99
|
+
}
|
|
100
|
+
this.pendingText = [];
|
|
101
|
+
if (this.finished) {
|
|
102
|
+
ws.send(JSON.stringify({ text: '' }));
|
|
103
|
+
}
|
|
104
|
+
});
|
|
105
|
+
ws.addEventListener('message', (event) => {
|
|
106
|
+
try {
|
|
107
|
+
const data = typeof event.data === 'string' ? event.data : '';
|
|
108
|
+
if (!data)
|
|
109
|
+
return;
|
|
110
|
+
const parsed = JSON.parse(data);
|
|
111
|
+
if (parsed.error) {
|
|
112
|
+
log.error('elevenlabs_error', { error: parsed.error, message: parsed.message });
|
|
113
|
+
this.endStream();
|
|
114
|
+
return;
|
|
115
|
+
}
|
|
116
|
+
if (typeof parsed.audio === 'string' && parsed.audio.length > 0) {
|
|
117
|
+
const bytes = Buffer.from(parsed.audio, 'base64');
|
|
118
|
+
this.consumePcmBytes(bytes);
|
|
119
|
+
}
|
|
120
|
+
if (parsed.isFinal) {
|
|
121
|
+
this.flushLeftover();
|
|
122
|
+
this.endStream();
|
|
123
|
+
}
|
|
124
|
+
}
|
|
125
|
+
catch (error) {
|
|
126
|
+
log.warn('elevenlabs_message_parse_failed', {
|
|
127
|
+
error: error instanceof Error ? error.message : String(error),
|
|
128
|
+
});
|
|
129
|
+
}
|
|
130
|
+
});
|
|
131
|
+
ws.addEventListener('close', () => {
|
|
132
|
+
this.flushLeftover();
|
|
133
|
+
this.endStream();
|
|
134
|
+
});
|
|
135
|
+
ws.addEventListener('error', (event) => {
|
|
136
|
+
log.warn('elevenlabs_ws_error', {
|
|
137
|
+
utteranceId: this.utteranceId,
|
|
138
|
+
message: 'message' in event ? String(event.message) : 'unknown',
|
|
139
|
+
});
|
|
140
|
+
this.endStream();
|
|
141
|
+
});
|
|
142
|
+
}
|
|
143
|
+
get sampleRate() {
|
|
144
|
+
return FORMAT_TO_SAMPLE_RATE[this.audioFormat];
|
|
145
|
+
}
|
|
146
|
+
consumePcmBytes(chunk) {
|
|
147
|
+
const combined = Buffer.concat([this.leftoverBytes, chunk]);
|
|
148
|
+
const bytesPerSample = 2;
|
|
149
|
+
const frameBytes = this.frameSamples * NUM_CHANNELS * bytesPerSample;
|
|
150
|
+
const fullFrames = Math.floor(combined.length / frameBytes);
|
|
151
|
+
let offset = 0;
|
|
152
|
+
for (let i = 0; i < fullFrames; i++) {
|
|
153
|
+
const frameSlice = combined.subarray(offset, offset + frameBytes);
|
|
154
|
+
offset += frameBytes;
|
|
155
|
+
const samples = new Int16Array(frameSlice.buffer, frameSlice.byteOffset, frameSlice.byteLength / 2);
|
|
156
|
+
// Copy because the underlying buffer may be reused.
|
|
157
|
+
const copied = new Int16Array(samples);
|
|
158
|
+
this.pushFrame({ samples: copied, sampleRate: this.sampleRate, numChannels: NUM_CHANNELS });
|
|
159
|
+
}
|
|
160
|
+
this.leftoverBytes = combined.subarray(offset);
|
|
161
|
+
}
|
|
162
|
+
flushLeftover() {
|
|
163
|
+
if (this.leftoverBytes.length < 2) {
|
|
164
|
+
this.leftoverBytes = Buffer.alloc(0);
|
|
165
|
+
return;
|
|
166
|
+
}
|
|
167
|
+
const evenLen = this.leftoverBytes.length - (this.leftoverBytes.length % 2);
|
|
168
|
+
if (evenLen === 0) {
|
|
169
|
+
this.leftoverBytes = Buffer.alloc(0);
|
|
170
|
+
return;
|
|
171
|
+
}
|
|
172
|
+
const samples = new Int16Array(this.leftoverBytes.buffer, this.leftoverBytes.byteOffset, evenLen / 2);
|
|
173
|
+
const copied = new Int16Array(samples);
|
|
174
|
+
this.leftoverBytes = Buffer.alloc(0);
|
|
175
|
+
this.pushFrame({ samples: copied, sampleRate: this.sampleRate, numChannels: NUM_CHANNELS });
|
|
176
|
+
}
|
|
177
|
+
pushFrame(frame) {
|
|
178
|
+
if (this.cancelled)
|
|
179
|
+
return;
|
|
180
|
+
const waiter = this.frameWaiters.shift();
|
|
181
|
+
if (waiter) {
|
|
182
|
+
waiter({ value: frame, done: false });
|
|
183
|
+
}
|
|
184
|
+
else {
|
|
185
|
+
this.frameQueue.push(frame);
|
|
186
|
+
}
|
|
187
|
+
}
|
|
188
|
+
endStream() {
|
|
189
|
+
if (this.endOfStream)
|
|
190
|
+
return;
|
|
191
|
+
this.endOfStream = true;
|
|
192
|
+
while (this.frameWaiters.length > 0) {
|
|
193
|
+
const w = this.frameWaiters.shift();
|
|
194
|
+
if (w)
|
|
195
|
+
w({ value: undefined, done: true });
|
|
196
|
+
}
|
|
197
|
+
}
|
|
198
|
+
pushText(delta) {
|
|
199
|
+
if (this.cancelled || this.finished)
|
|
200
|
+
return;
|
|
201
|
+
if (!this.ws || !this.opened) {
|
|
202
|
+
this.pendingText.push(delta);
|
|
203
|
+
return;
|
|
204
|
+
}
|
|
205
|
+
try {
|
|
206
|
+
this.ws.send(JSON.stringify({ text: delta }));
|
|
207
|
+
}
|
|
208
|
+
catch (error) {
|
|
209
|
+
log.warn('elevenlabs_send_failed', {
|
|
210
|
+
message: error instanceof Error ? error.message : String(error),
|
|
211
|
+
});
|
|
212
|
+
}
|
|
213
|
+
}
|
|
214
|
+
finish() {
|
|
215
|
+
if (this.cancelled || this.finished)
|
|
216
|
+
return;
|
|
217
|
+
this.finished = true;
|
|
218
|
+
if (this.ws && this.opened) {
|
|
219
|
+
try {
|
|
220
|
+
// Empty text signals end of input per ElevenLabs WS protocol.
|
|
221
|
+
this.ws.send(JSON.stringify({ text: '' }));
|
|
222
|
+
}
|
|
223
|
+
catch {
|
|
224
|
+
// ignore
|
|
225
|
+
}
|
|
226
|
+
}
|
|
227
|
+
}
|
|
228
|
+
cancel() {
|
|
229
|
+
if (this.cancelled)
|
|
230
|
+
return;
|
|
231
|
+
this.cancelled = true;
|
|
232
|
+
this.frameQueue = [];
|
|
233
|
+
if (this.ws) {
|
|
234
|
+
try {
|
|
235
|
+
this.ws.close();
|
|
236
|
+
}
|
|
237
|
+
catch {
|
|
238
|
+
// ignore
|
|
239
|
+
}
|
|
240
|
+
}
|
|
241
|
+
this.endStream();
|
|
242
|
+
}
|
|
243
|
+
frames() {
|
|
244
|
+
const self = this;
|
|
245
|
+
return {
|
|
246
|
+
[Symbol.asyncIterator]() {
|
|
247
|
+
return {
|
|
248
|
+
next() {
|
|
249
|
+
if (self.cancelled) {
|
|
250
|
+
return Promise.resolve({ value: undefined, done: true });
|
|
251
|
+
}
|
|
252
|
+
const queued = self.frameQueue.shift();
|
|
253
|
+
if (queued) {
|
|
254
|
+
return Promise.resolve({ value: queued, done: false });
|
|
255
|
+
}
|
|
256
|
+
if (self.endOfStream) {
|
|
257
|
+
return Promise.resolve({ value: undefined, done: true });
|
|
258
|
+
}
|
|
259
|
+
return new Promise((resolve) => {
|
|
260
|
+
self.frameWaiters.push(resolve);
|
|
261
|
+
});
|
|
262
|
+
},
|
|
263
|
+
};
|
|
264
|
+
},
|
|
265
|
+
};
|
|
266
|
+
}
|
|
267
|
+
}
|
|
268
|
+
//# sourceMappingURL=elevenlabs.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"elevenlabs.js","sourceRoot":"","sources":["../../src/tts/elevenlabs.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,22 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* @file TTS engine factory.
|
|
3
|
+
*/
|
|
4
|
+
import { type ElevenLabsAudioFormat } from './elevenlabs.js';
|
|
5
|
+
import type { TtsEngine } from './types.js';
|
|
6
|
+
export type TtsProvider = 'elevenlabs' | 'openai';
|
|
7
|
+
export interface TtsFactoryOptions {
|
|
8
|
+
provider: TtsProvider;
|
|
9
|
+
apiKey: string;
|
|
10
|
+
voiceId: string;
|
|
11
|
+
modelId: string;
|
|
12
|
+
/** ElevenLabs-only — sample-rate selector. */
|
|
13
|
+
audioFormat?: ElevenLabsAudioFormat;
|
|
14
|
+
/** OpenAI-only — natural-language voice instructions (gpt-4o-tts family). */
|
|
15
|
+
instructions?: string;
|
|
16
|
+
/** OpenAI-only — speech speed multiplier in [0.25, 4.0]. */
|
|
17
|
+
speed?: number;
|
|
18
|
+
}
|
|
19
|
+
export declare function createTtsEngine(options: TtsFactoryOptions): TtsEngine;
|
|
20
|
+
export type { TtsEngine, TtsFrame, TtsUtterance } from './types.js';
|
|
21
|
+
export type { ElevenLabsAudioFormat } from './elevenlabs.js';
|
|
22
|
+
//# sourceMappingURL=index.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../src/tts/index.ts"],"names":[],"mappings":"AAAA;;GAEG;AAEH,OAAO,EAAoB,KAAK,qBAAqB,EAAE,MAAM,iBAAiB,CAAC;AAE/E,OAAO,KAAK,EAAE,SAAS,EAAE,MAAM,YAAY,CAAC;AAE5C,MAAM,MAAM,WAAW,GAAG,YAAY,GAAG,QAAQ,CAAC;AAElD,MAAM,WAAW,iBAAiB;IAChC,QAAQ,EAAE,WAAW,CAAC;IACtB,MAAM,EAAE,MAAM,CAAC;IACf,OAAO,EAAE,MAAM,CAAC;IAChB,OAAO,EAAE,MAAM,CAAC;IAChB,8CAA8C;IAC9C,WAAW,CAAC,EAAE,qBAAqB,CAAC;IACpC,6EAA6E;IAC7E,YAAY,CAAC,EAAE,MAAM,CAAC;IACtB,4DAA4D;IAC5D,KAAK,CAAC,EAAE,MAAM,CAAC;CAChB;AAED,wBAAgB,eAAe,CAAC,OAAO,EAAE,iBAAiB,GAAG,SAAS,CAgBrE;AAED,YAAY,EAAE,SAAS,EAAE,QAAQ,EAAE,YAAY,EAAE,MAAM,YAAY,CAAC;AACpE,YAAY,EAAE,qBAAqB,EAAE,MAAM,iBAAiB,CAAC"}
|
|
@@ -0,0 +1,23 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* @file TTS engine factory.
|
|
3
|
+
*/
|
|
4
|
+
import { ElevenLabsEngine } from './elevenlabs.js';
|
|
5
|
+
import { OpenAiTtsEngine } from './openai.js';
|
|
6
|
+
export function createTtsEngine(options) {
|
|
7
|
+
if (options.provider === 'openai') {
|
|
8
|
+
return new OpenAiTtsEngine({
|
|
9
|
+
apiKey: options.apiKey,
|
|
10
|
+
voice: options.voiceId,
|
|
11
|
+
model: options.modelId,
|
|
12
|
+
...(options.instructions ? { instructions: options.instructions } : {}),
|
|
13
|
+
...(typeof options.speed === 'number' ? { speed: options.speed } : {}),
|
|
14
|
+
});
|
|
15
|
+
}
|
|
16
|
+
return new ElevenLabsEngine({
|
|
17
|
+
apiKey: options.apiKey,
|
|
18
|
+
voiceId: options.voiceId,
|
|
19
|
+
modelId: options.modelId,
|
|
20
|
+
...(options.audioFormat ? { audioFormat: options.audioFormat } : {}),
|
|
21
|
+
});
|
|
22
|
+
}
|
|
23
|
+
//# sourceMappingURL=index.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"index.js","sourceRoot":"","sources":["../../src/tts/index.ts"],"names":[],"mappings":"AAAA;;GAEG;AAEH,OAAO,EAAE,gBAAgB,EAA8B,MAAM,iBAAiB,CAAC;AAC/E,OAAO,EAAE,eAAe,EAAE,MAAM,aAAa,CAAC;AAkB9C,MAAM,UAAU,eAAe,CAAC,OAA0B;IACxD,IAAI,OAAO,CAAC,QAAQ,KAAK,QAAQ,EAAE,CAAC;QAClC,OAAO,IAAI,eAAe,CAAC;YACzB,MAAM,EAAE,OAAO,CAAC,MAAM;YACtB,KAAK,EAAE,OAAO,CAAC,OAAO;YACtB,KAAK,EAAE,OAAO,CAAC,OAAO;YACtB,GAAG,CAAC,OAAO,CAAC,YAAY,CAAC,CAAC,CAAC,EAAE,YAAY,EAAE,OAAO,CAAC,YAAY,EAAE,CAAC,CAAC,CAAC,EAAE,CAAC;YACvE,GAAG,CAAC,OAAO,OAAO,CAAC,KAAK,KAAK,QAAQ,CAAC,CAAC,CAAC,EAAE,KAAK,EAAE,OAAO,CAAC,KAAK,EAAE,CAAC,CAAC,CAAC,EAAE,CAAC;SACvE,CAAC,CAAC;IACL,CAAC;IACD,OAAO,IAAI,gBAAgB,CAAC;QAC1B,MAAM,EAAE,OAAO,CAAC,MAAM;QACtB,OAAO,EAAE,OAAO,CAAC,OAAO;QACxB,OAAO,EAAE,OAAO,CAAC,OAAO;QACxB,GAAG,CAAC,OAAO,CAAC,WAAW,CAAC,CAAC,CAAC,EAAE,WAAW,EAAE,OAAO,CAAC,WAAW,EAAE,CAAC,CAAC,CAAC,EAAE,CAAC;KACrE,CAAC,CAAC;AACL,CAAC"}
|
|
@@ -0,0 +1,35 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* @file OpenAI TTS engine (`/v1/audio/speech`).
|
|
3
|
+
*
|
|
4
|
+
* OpenAI's TTS doesn't expose a per-token WebSocket like ElevenLabs — it's a
|
|
5
|
+
* single HTTP request per chunk of text, with the response body streaming the
|
|
6
|
+
* audio bytes as they're generated. To keep latency reasonable for
|
|
7
|
+
* meeting-length replies we segment incoming text by sentence and dispatch
|
|
8
|
+
* one HTTP request per sentence; bytes from each request are pushed into the
|
|
9
|
+
* frame queue in order so the agent's voice plays back smoothly.
|
|
10
|
+
*
|
|
11
|
+
* We request `response_format: 'pcm'`, which is signed 16-bit little-endian
|
|
12
|
+
* mono at 24 kHz — a direct match for the LiveKit AudioSource format we
|
|
13
|
+
* already use elsewhere.
|
|
14
|
+
*/
|
|
15
|
+
import type { TtsEngine, TtsUtterance } from './types.js';
|
|
16
|
+
export type OpenAiTtsModel = 'tts-1' | 'tts-1-hd' | 'gpt-4o-mini-tts' | 'gpt-4o-tts' | string;
|
|
17
|
+
export type OpenAiTtsVoice = 'alloy' | 'ash' | 'ballad' | 'coral' | 'echo' | 'fable' | 'nova' | 'onyx' | 'sage' | 'shimmer' | 'verse' | string;
|
|
18
|
+
export interface OpenAiTtsEngineOptions {
|
|
19
|
+
apiKey: string;
|
|
20
|
+
voice: OpenAiTtsVoice;
|
|
21
|
+
model: OpenAiTtsModel;
|
|
22
|
+
/** Optional natural-language instructions (only honored by gpt-4o-tts family). */
|
|
23
|
+
instructions?: string;
|
|
24
|
+
/** Speech speed multiplier in [0.25, 4.0]; default 1.0. */
|
|
25
|
+
speed?: number;
|
|
26
|
+
}
|
|
27
|
+
export declare class OpenAiTtsEngine implements TtsEngine {
|
|
28
|
+
private readonly options;
|
|
29
|
+
readonly sampleRate = 24000;
|
|
30
|
+
readonly numChannels = 1;
|
|
31
|
+
constructor(options: OpenAiTtsEngineOptions);
|
|
32
|
+
beginUtterance(utteranceId: string): TtsUtterance;
|
|
33
|
+
close(): Promise<void>;
|
|
34
|
+
}
|
|
35
|
+
//# sourceMappingURL=openai.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"openai.d.ts","sourceRoot":"","sources":["../../src/tts/openai.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;GAaG;AAIH,OAAO,KAAK,EAAE,SAAS,EAAY,YAAY,EAAE,MAAM,YAAY,CAAC;AAQpE,MAAM,MAAM,cAAc,GAAG,OAAO,GAAG,UAAU,GAAG,iBAAiB,GAAG,YAAY,GAAG,MAAM,CAAC;AAC9F,MAAM,MAAM,cAAc,GACtB,OAAO,GACP,KAAK,GACL,QAAQ,GACR,OAAO,GACP,MAAM,GACN,OAAO,GACP,MAAM,GACN,MAAM,GACN,MAAM,GACN,SAAS,GACT,OAAO,GACP,MAAM,CAAC;AAEX,MAAM,WAAW,sBAAsB;IACrC,MAAM,EAAE,MAAM,CAAC;IACf,KAAK,EAAE,cAAc,CAAC;IACtB,KAAK,EAAE,cAAc,CAAC;IACtB,kFAAkF;IAClF,YAAY,CAAC,EAAE,MAAM,CAAC;IACtB,2DAA2D;IAC3D,KAAK,CAAC,EAAE,MAAM,CAAC;CAChB;AAED,qBAAa,eAAgB,YAAW,SAAS;IAInC,OAAO,CAAC,QAAQ,CAAC,OAAO;IAHpC,QAAQ,CAAC,UAAU,SAAe;IAClC,QAAQ,CAAC,WAAW,KAAgB;gBAEP,OAAO,EAAE,sBAAsB;IAE5D,cAAc,CAAC,WAAW,EAAE,MAAM,GAAG,YAAY;IAI3C,KAAK,IAAI,OAAO,CAAC,IAAI,CAAC;CAG7B"}
|
|
@@ -0,0 +1,270 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* @file OpenAI TTS engine (`/v1/audio/speech`).
|
|
3
|
+
*
|
|
4
|
+
* OpenAI's TTS doesn't expose a per-token WebSocket like ElevenLabs — it's a
|
|
5
|
+
* single HTTP request per chunk of text, with the response body streaming the
|
|
6
|
+
* audio bytes as they're generated. To keep latency reasonable for
|
|
7
|
+
* meeting-length replies we segment incoming text by sentence and dispatch
|
|
8
|
+
* one HTTP request per sentence; bytes from each request are pushed into the
|
|
9
|
+
* frame queue in order so the agent's voice plays back smoothly.
|
|
10
|
+
*
|
|
11
|
+
* We request `response_format: 'pcm'`, which is signed 16-bit little-endian
|
|
12
|
+
* mono at 24 kHz — a direct match for the LiveKit AudioSource format we
|
|
13
|
+
* already use elsewhere.
|
|
14
|
+
*/
|
|
15
|
+
import { request } from 'undici';
|
|
16
|
+
import { log } from '../logging.js';
|
|
17
|
+
const OPENAI_TTS_URL = 'https://api.openai.com/v1/audio/speech';
|
|
18
|
+
const SAMPLE_RATE = 24_000;
|
|
19
|
+
const NUM_CHANNELS = 1;
|
|
20
|
+
/** 20 ms framing — 480 samples @ 24 kHz mono. */
|
|
21
|
+
const FRAME_SAMPLES = 480;
|
|
22
|
+
export class OpenAiTtsEngine {
|
|
23
|
+
options;
|
|
24
|
+
sampleRate = SAMPLE_RATE;
|
|
25
|
+
numChannels = NUM_CHANNELS;
|
|
26
|
+
constructor(options) {
|
|
27
|
+
this.options = options;
|
|
28
|
+
}
|
|
29
|
+
beginUtterance(utteranceId) {
|
|
30
|
+
return new OpenAiTtsUtterance(utteranceId, this.options);
|
|
31
|
+
}
|
|
32
|
+
async close() {
|
|
33
|
+
// No persistent resources at the engine level.
|
|
34
|
+
}
|
|
35
|
+
}
|
|
36
|
+
class OpenAiTtsUtterance {
|
|
37
|
+
utteranceId;
|
|
38
|
+
options;
|
|
39
|
+
buffer = '';
|
|
40
|
+
finished = false;
|
|
41
|
+
cancelled = false;
|
|
42
|
+
frameQueue = [];
|
|
43
|
+
frameWaiters = [];
|
|
44
|
+
endOfStream = false;
|
|
45
|
+
/** Sentences waiting to be sent to the API, FIFO. */
|
|
46
|
+
sentenceQueue = [];
|
|
47
|
+
/** Whether a worker is actively draining the sentence queue. */
|
|
48
|
+
workerRunning = false;
|
|
49
|
+
/** AbortController for any in-flight HTTP request. */
|
|
50
|
+
currentAbort = null;
|
|
51
|
+
/** Bytes that didn't form a complete frame on the last chunk. */
|
|
52
|
+
partialPcm = Buffer.alloc(0);
|
|
53
|
+
constructor(utteranceId, options) {
|
|
54
|
+
this.utteranceId = utteranceId;
|
|
55
|
+
this.options = options;
|
|
56
|
+
}
|
|
57
|
+
pushText(delta) {
|
|
58
|
+
if (this.cancelled || this.finished)
|
|
59
|
+
return;
|
|
60
|
+
this.buffer += delta;
|
|
61
|
+
this.flushSentences();
|
|
62
|
+
}
|
|
63
|
+
finish() {
|
|
64
|
+
if (this.cancelled || this.finished)
|
|
65
|
+
return;
|
|
66
|
+
this.finished = true;
|
|
67
|
+
if (this.buffer.trim().length > 0) {
|
|
68
|
+
this.sentenceQueue.push(this.buffer);
|
|
69
|
+
this.buffer = '';
|
|
70
|
+
}
|
|
71
|
+
void this.runWorker();
|
|
72
|
+
}
|
|
73
|
+
cancel() {
|
|
74
|
+
if (this.cancelled)
|
|
75
|
+
return;
|
|
76
|
+
this.cancelled = true;
|
|
77
|
+
this.sentenceQueue = [];
|
|
78
|
+
this.buffer = '';
|
|
79
|
+
this.frameQueue = [];
|
|
80
|
+
if (this.currentAbort)
|
|
81
|
+
this.currentAbort.abort();
|
|
82
|
+
this.endStream();
|
|
83
|
+
}
|
|
84
|
+
frames() {
|
|
85
|
+
const self = this;
|
|
86
|
+
return {
|
|
87
|
+
[Symbol.asyncIterator]() {
|
|
88
|
+
return {
|
|
89
|
+
next() {
|
|
90
|
+
if (self.cancelled) {
|
|
91
|
+
return Promise.resolve({ value: undefined, done: true });
|
|
92
|
+
}
|
|
93
|
+
const queued = self.frameQueue.shift();
|
|
94
|
+
if (queued) {
|
|
95
|
+
return Promise.resolve({ value: queued, done: false });
|
|
96
|
+
}
|
|
97
|
+
if (self.endOfStream) {
|
|
98
|
+
return Promise.resolve({ value: undefined, done: true });
|
|
99
|
+
}
|
|
100
|
+
return new Promise((resolve) => {
|
|
101
|
+
self.frameWaiters.push(resolve);
|
|
102
|
+
});
|
|
103
|
+
},
|
|
104
|
+
};
|
|
105
|
+
},
|
|
106
|
+
};
|
|
107
|
+
}
|
|
108
|
+
/** Drains complete sentences out of the buffer into the request queue. */
|
|
109
|
+
flushSentences() {
|
|
110
|
+
const SENTENCE_BREAK = /([.!?]["')\]]?)(\s+)/;
|
|
111
|
+
while (true) {
|
|
112
|
+
const match = this.buffer.match(SENTENCE_BREAK);
|
|
113
|
+
if (!match)
|
|
114
|
+
break;
|
|
115
|
+
const idx = match.index ?? -1;
|
|
116
|
+
if (idx < 0)
|
|
117
|
+
break;
|
|
118
|
+
const cutoff = idx + match[1].length;
|
|
119
|
+
const sentence = this.buffer.slice(0, cutoff).trim();
|
|
120
|
+
this.buffer = this.buffer.slice(cutoff + match[2].length);
|
|
121
|
+
if (sentence.length === 0)
|
|
122
|
+
continue;
|
|
123
|
+
this.sentenceQueue.push(sentence);
|
|
124
|
+
}
|
|
125
|
+
void this.runWorker();
|
|
126
|
+
}
|
|
127
|
+
async runWorker() {
|
|
128
|
+
if (this.workerRunning)
|
|
129
|
+
return;
|
|
130
|
+
this.workerRunning = true;
|
|
131
|
+
try {
|
|
132
|
+
while (!this.cancelled) {
|
|
133
|
+
const next = this.sentenceQueue.shift();
|
|
134
|
+
if (!next) {
|
|
135
|
+
// No more sentences right now; if we're done streaming, drain.
|
|
136
|
+
if (this.finished && !this.cancelled) {
|
|
137
|
+
this.flushLeftoverByte();
|
|
138
|
+
this.endStream();
|
|
139
|
+
}
|
|
140
|
+
break;
|
|
141
|
+
}
|
|
142
|
+
await this.synthesize(next);
|
|
143
|
+
if (this.cancelled)
|
|
144
|
+
break;
|
|
145
|
+
}
|
|
146
|
+
}
|
|
147
|
+
finally {
|
|
148
|
+
this.workerRunning = false;
|
|
149
|
+
if (!this.cancelled && this.finished && this.sentenceQueue.length === 0) {
|
|
150
|
+
this.flushLeftoverByte();
|
|
151
|
+
this.endStream();
|
|
152
|
+
}
|
|
153
|
+
}
|
|
154
|
+
}
|
|
155
|
+
async synthesize(text) {
|
|
156
|
+
if (this.cancelled)
|
|
157
|
+
return;
|
|
158
|
+
const abort = new AbortController();
|
|
159
|
+
this.currentAbort = abort;
|
|
160
|
+
const body = {
|
|
161
|
+
model: this.options.model,
|
|
162
|
+
voice: this.options.voice,
|
|
163
|
+
input: text,
|
|
164
|
+
response_format: 'pcm',
|
|
165
|
+
};
|
|
166
|
+
if (this.options.instructions)
|
|
167
|
+
body.instructions = this.options.instructions;
|
|
168
|
+
if (typeof this.options.speed === 'number')
|
|
169
|
+
body.speed = this.options.speed;
|
|
170
|
+
log.debug('openai_tts_request_started', {
|
|
171
|
+
utteranceId: this.utteranceId,
|
|
172
|
+
textLen: text.length,
|
|
173
|
+
model: this.options.model,
|
|
174
|
+
voice: this.options.voice,
|
|
175
|
+
});
|
|
176
|
+
try {
|
|
177
|
+
const response = await request(OPENAI_TTS_URL, {
|
|
178
|
+
method: 'POST',
|
|
179
|
+
headers: {
|
|
180
|
+
'content-type': 'application/json',
|
|
181
|
+
authorization: `Bearer ${this.options.apiKey}`,
|
|
182
|
+
},
|
|
183
|
+
body: JSON.stringify(body),
|
|
184
|
+
signal: abort.signal,
|
|
185
|
+
});
|
|
186
|
+
if (response.statusCode !== 200) {
|
|
187
|
+
const errBody = await response.body.text().catch(() => '');
|
|
188
|
+
log.error('openai_tts_non_2xx', {
|
|
189
|
+
status: response.statusCode,
|
|
190
|
+
body: errBody.slice(0, 400),
|
|
191
|
+
});
|
|
192
|
+
return;
|
|
193
|
+
}
|
|
194
|
+
let totalBytes = 0;
|
|
195
|
+
for await (const chunk of response.body) {
|
|
196
|
+
if (this.cancelled)
|
|
197
|
+
return;
|
|
198
|
+
const buf = Buffer.isBuffer(chunk) ? chunk : Buffer.from(chunk);
|
|
199
|
+
totalBytes += buf.length;
|
|
200
|
+
this.consumePcmBytes(buf);
|
|
201
|
+
}
|
|
202
|
+
log.debug('openai_tts_request_completed', {
|
|
203
|
+
utteranceId: this.utteranceId,
|
|
204
|
+
bytes: totalBytes,
|
|
205
|
+
});
|
|
206
|
+
}
|
|
207
|
+
catch (error) {
|
|
208
|
+
if (this.cancelled)
|
|
209
|
+
return;
|
|
210
|
+
log.error('openai_tts_request_failed', {
|
|
211
|
+
message: error instanceof Error ? error.message : String(error),
|
|
212
|
+
});
|
|
213
|
+
}
|
|
214
|
+
finally {
|
|
215
|
+
if (this.currentAbort === abort)
|
|
216
|
+
this.currentAbort = null;
|
|
217
|
+
}
|
|
218
|
+
}
|
|
219
|
+
consumePcmBytes(chunk) {
|
|
220
|
+
if (chunk.length === 0)
|
|
221
|
+
return;
|
|
222
|
+
this.partialPcm = Buffer.concat([this.partialPcm, chunk]);
|
|
223
|
+
const bytesPerSample = 2;
|
|
224
|
+
const frameBytes = FRAME_SAMPLES * NUM_CHANNELS * bytesPerSample;
|
|
225
|
+
let offset = 0;
|
|
226
|
+
while (offset + frameBytes <= this.partialPcm.length) {
|
|
227
|
+
const slice = this.partialPcm.subarray(offset, offset + frameBytes);
|
|
228
|
+
offset += frameBytes;
|
|
229
|
+
const samples = new Int16Array(slice.buffer, slice.byteOffset, slice.byteLength / 2);
|
|
230
|
+
const copied = new Int16Array(samples);
|
|
231
|
+
this.pushFrame({ samples: copied, sampleRate: SAMPLE_RATE, numChannels: NUM_CHANNELS });
|
|
232
|
+
}
|
|
233
|
+
this.partialPcm = offset > 0 ? Buffer.from(this.partialPcm.subarray(offset)) : this.partialPcm;
|
|
234
|
+
}
|
|
235
|
+
flushLeftoverByte() {
|
|
236
|
+
// Drop any leftover bytes that don't form complete samples (rare).
|
|
237
|
+
const evenLen = this.partialPcm.length - (this.partialPcm.length % 2);
|
|
238
|
+
if (evenLen > 0) {
|
|
239
|
+
const slice = this.partialPcm.subarray(0, evenLen);
|
|
240
|
+
const samples = new Int16Array(slice.buffer, slice.byteOffset, slice.byteLength / 2);
|
|
241
|
+
const copied = new Int16Array(samples);
|
|
242
|
+
this.pushFrame({ samples: copied, sampleRate: SAMPLE_RATE, numChannels: NUM_CHANNELS });
|
|
243
|
+
}
|
|
244
|
+
this.partialPcm = Buffer.alloc(0);
|
|
245
|
+
}
|
|
246
|
+
pushFrame(frame) {
|
|
247
|
+
if (this.cancelled)
|
|
248
|
+
return;
|
|
249
|
+
const waiter = this.frameWaiters.shift();
|
|
250
|
+
if (waiter) {
|
|
251
|
+
waiter({ value: frame, done: false });
|
|
252
|
+
}
|
|
253
|
+
else {
|
|
254
|
+
this.frameQueue.push(frame);
|
|
255
|
+
}
|
|
256
|
+
}
|
|
257
|
+
endStream() {
|
|
258
|
+
if (this.endOfStream)
|
|
259
|
+
return;
|
|
260
|
+
this.endOfStream = true;
|
|
261
|
+
while (this.frameWaiters.length > 0) {
|
|
262
|
+
const w = this.frameWaiters.shift();
|
|
263
|
+
if (w)
|
|
264
|
+
w({ value: undefined, done: true });
|
|
265
|
+
}
|
|
266
|
+
}
|
|
267
|
+
}
|
|
268
|
+
// Mark utteranceId as used so TS doesn't complain when log statements omit it.
|
|
269
|
+
void OpenAiTtsUtterance;
|
|
270
|
+
//# sourceMappingURL=openai.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"openai.js","sourceRoot":"","sources":["../../src/tts/openai.ts"],"names":[],"mappings":"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"}
|