@shogo-ai/voice 1.4.1 → 1.9.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/{chunk-ZGYHDC5G.js → chunk-B4JC3DK2.js} +107 -1
- package/dist/{chunk-QHDIEE7O.js → chunk-O362EJQR.js} +73 -2
- package/dist/{chunk-D2EVSUZX.js → chunk-T3T4EBWY.js} +3 -2
- package/dist/index.cjs +107 -0
- package/dist/index.d.cts +2 -2
- package/dist/index.d.ts +2 -2
- package/dist/index.js +3 -1
- package/dist/route/agent.cjs +176 -0
- package/dist/route/agent.js +3 -3
- package/dist/route/audio-tags.cjs +176 -0
- package/dist/route/audio-tags.js +3 -3
- package/dist/route/index.cjs +177 -0
- package/dist/route/index.d.cts +5 -1
- package/dist/route/index.d.ts +5 -1
- package/dist/route/index.js +3 -3
- package/dist/route/music.cjs +1151 -0
- package/dist/route/music.d.cts +22 -0
- package/dist/route/music.d.ts +22 -0
- package/dist/route/music.js +13 -0
- package/dist/route/signed-url.cjs +176 -0
- package/dist/route/signed-url.js +3 -3
- package/dist/route/tts-preview.cjs +176 -0
- package/dist/route/tts-preview.js +3 -3
- package/dist/server.cjs +176 -0
- package/dist/server.d.cts +7 -1
- package/dist/server.d.ts +7 -1
- package/dist/server.js +2 -2
- package/dist/{types-BKJtOUbE.d.ts → types-W9y6eMj3.d.cts} +80 -1
- package/dist/{types-BKJtOUbE.d.cts → types-W9y6eMj3.d.ts} +80 -1
- package/package.json +7 -2
|
@@ -44,6 +44,7 @@ function resolveConvaiTtsModel(requested) {
|
|
|
44
44
|
if (requested && CONVAI_SUPPORTED_TTS_MODELS.has(requested)) return requested;
|
|
45
45
|
return CONVAI_TTS_MODEL_FALLBACK;
|
|
46
46
|
}
|
|
47
|
+
var DEFAULT_MUSIC_MODEL = "music_v1";
|
|
47
48
|
var ElevenLabsApiError = class extends Error {
|
|
48
49
|
constructor(message, status, body) {
|
|
49
50
|
super(message);
|
|
@@ -244,6 +245,110 @@ var ElevenLabsClient = class {
|
|
|
244
245
|
const contentType = res.headers.get("content-type") ?? accept;
|
|
245
246
|
return { audio, modelId: used, contentType };
|
|
246
247
|
}
|
|
248
|
+
/**
|
|
249
|
+
* Low-level escape hatch for any ElevenLabs endpoint not covered by a
|
|
250
|
+
* dedicated method (music, sound-generation, speech-to-text, dubbing,
|
|
251
|
+
* voice-design, …). Handles auth, query encoding, JSON body encoding,
|
|
252
|
+
* and JSON-vs-binary response parsing; non-2xx responses throw
|
|
253
|
+
* {@link ElevenLabsApiError} like the typed methods do.
|
|
254
|
+
*
|
|
255
|
+
* @example
|
|
256
|
+
* ```ts
|
|
257
|
+
* const { json } = await el.request({ method: 'GET', path: '/v1/models' })
|
|
258
|
+
* const { audio } = await el.request({
|
|
259
|
+
* method: 'POST',
|
|
260
|
+
* path: '/v1/sound-generation',
|
|
261
|
+
* body: { text: 'door creak' },
|
|
262
|
+
* accept: 'audio/mpeg',
|
|
263
|
+
* })
|
|
264
|
+
* ```
|
|
265
|
+
*/
|
|
266
|
+
async request(opts) {
|
|
267
|
+
const accept = opts.accept ?? "application/json";
|
|
268
|
+
let url = `${this.baseUrl}${opts.path.startsWith("/") ? "" : "/"}${opts.path}`;
|
|
269
|
+
if (opts.query) {
|
|
270
|
+
const params = new URLSearchParams();
|
|
271
|
+
for (const [k, v] of Object.entries(opts.query)) {
|
|
272
|
+
if (v !== null && v !== void 0) params.set(k, String(v));
|
|
273
|
+
}
|
|
274
|
+
const qs = params.toString();
|
|
275
|
+
if (qs) url += `${url.includes("?") ? "&" : "?"}${qs}`;
|
|
276
|
+
}
|
|
277
|
+
const headers = this.headers({ accept, ...opts.headers ?? {} });
|
|
278
|
+
const init = { method: opts.method, headers };
|
|
279
|
+
if (opts.body !== void 0 && opts.body !== null) {
|
|
280
|
+
const isPlainPayload = typeof opts.body === "object" && !(opts.body instanceof ArrayBuffer) && !(opts.body instanceof Uint8Array) && !(typeof Blob !== "undefined" && opts.body instanceof Blob) && !(typeof FormData !== "undefined" && opts.body instanceof FormData);
|
|
281
|
+
if (isPlainPayload) {
|
|
282
|
+
headers["content-type"] = "application/json";
|
|
283
|
+
init.body = JSON.stringify(opts.body);
|
|
284
|
+
} else {
|
|
285
|
+
init.body = opts.body;
|
|
286
|
+
}
|
|
287
|
+
}
|
|
288
|
+
const res = await this.fetchImpl(url, init);
|
|
289
|
+
if (!res.ok) {
|
|
290
|
+
const text = await res.text();
|
|
291
|
+
throw new ElevenLabsApiError(
|
|
292
|
+
`request ${opts.method} ${opts.path} failed: ${res.status}`,
|
|
293
|
+
res.status,
|
|
294
|
+
text
|
|
295
|
+
);
|
|
296
|
+
}
|
|
297
|
+
const contentType = res.headers.get("content-type") ?? accept;
|
|
298
|
+
if (contentType.includes("application/json")) {
|
|
299
|
+
return { status: res.status, contentType, json: await res.json() };
|
|
300
|
+
}
|
|
301
|
+
return { status: res.status, contentType, audio: await res.arrayBuffer() };
|
|
302
|
+
}
|
|
303
|
+
/**
|
|
304
|
+
* Generate music via `POST /v1/music`. Provide exactly one of `prompt`
|
|
305
|
+
* or `compositionPlan` (ElevenLabs returns 422 otherwise). Returns the
|
|
306
|
+
* raw audio bytes plus the response content type.
|
|
307
|
+
*
|
|
308
|
+
* @example
|
|
309
|
+
* ```ts
|
|
310
|
+
* const { audio } = await el.composeMusic({
|
|
311
|
+
* prompt: 'Upbeat synthwave with driving bass',
|
|
312
|
+
* musicLengthMs: 30_000,
|
|
313
|
+
* })
|
|
314
|
+
* ```
|
|
315
|
+
*/
|
|
316
|
+
async composeMusic(params) {
|
|
317
|
+
const hasPrompt = typeof params.prompt === "string" && params.prompt.length > 0;
|
|
318
|
+
const hasPlan = params.compositionPlan != null;
|
|
319
|
+
if (hasPrompt === hasPlan) {
|
|
320
|
+
throw new Error(
|
|
321
|
+
"composeMusic: provide exactly one of `prompt` or `compositionPlan`"
|
|
322
|
+
);
|
|
323
|
+
}
|
|
324
|
+
const body = {
|
|
325
|
+
model_id: params.modelId ?? DEFAULT_MUSIC_MODEL
|
|
326
|
+
};
|
|
327
|
+
if (hasPrompt) {
|
|
328
|
+
body.prompt = params.prompt;
|
|
329
|
+
if (params.musicLengthMs !== void 0) body.music_length_ms = params.musicLengthMs;
|
|
330
|
+
if (params.forceInstrumental !== void 0) {
|
|
331
|
+
body.force_instrumental = params.forceInstrumental;
|
|
332
|
+
}
|
|
333
|
+
} else {
|
|
334
|
+
body.composition_plan = params.compositionPlan;
|
|
335
|
+
}
|
|
336
|
+
const { audio, contentType } = await this.request({
|
|
337
|
+
method: "POST",
|
|
338
|
+
path: "/v1/music",
|
|
339
|
+
body,
|
|
340
|
+
accept: "audio/mpeg",
|
|
341
|
+
...params.outputFormat ? { query: { output_format: params.outputFormat } } : {}
|
|
342
|
+
});
|
|
343
|
+
if (!audio) {
|
|
344
|
+
throw new ElevenLabsApiError(
|
|
345
|
+
"composeMusic: expected audio bytes but got a JSON response",
|
|
346
|
+
500,
|
|
347
|
+
""
|
|
348
|
+
);
|
|
349
|
+
}
|
|
350
|
+
return { audio, contentType };
|
|
351
|
+
}
|
|
247
352
|
/** Probe whether a given voice id still exists on the caller's account. */
|
|
248
353
|
async voiceExists(voiceId) {
|
|
249
354
|
const res = await this.fetchImpl(
|
|
@@ -416,8 +521,9 @@ export {
|
|
|
416
521
|
CONVAI_SUPPORTED_TTS_MODELS,
|
|
417
522
|
CONVAI_TTS_MODEL_FALLBACK,
|
|
418
523
|
resolveConvaiTtsModel,
|
|
524
|
+
DEFAULT_MUSIC_MODEL,
|
|
419
525
|
ElevenLabsApiError,
|
|
420
526
|
ElevenLabsClient,
|
|
421
527
|
MEMORY_CLIENT_TOOLS
|
|
422
528
|
};
|
|
423
|
-
//# sourceMappingURL=chunk-
|
|
529
|
+
//# sourceMappingURL=chunk-B4JC3DK2.js.map
|
|
@@ -5,7 +5,7 @@ import {
|
|
|
5
5
|
MEMORY_CLIENT_TOOLS,
|
|
6
6
|
extractBasePrompt,
|
|
7
7
|
resolveConvaiTtsModel
|
|
8
|
-
} from "./chunk-
|
|
8
|
+
} from "./chunk-B4JC3DK2.js";
|
|
9
9
|
import {
|
|
10
10
|
AUDIO_TAGS,
|
|
11
11
|
AUDIO_TAG_GROUPS,
|
|
@@ -39,6 +39,28 @@ async function readJson(req) {
|
|
|
39
39
|
}
|
|
40
40
|
var NOOP_LOGGER = () => {
|
|
41
41
|
};
|
|
42
|
+
function parseMusicBody(raw) {
|
|
43
|
+
if (raw === null || typeof raw !== "object") return { error: "Invalid JSON body" };
|
|
44
|
+
const b = raw;
|
|
45
|
+
const hasPrompt = typeof b.prompt === "string" && b.prompt.trim().length > 0;
|
|
46
|
+
const hasPlan = b.compositionPlan != null && typeof b.compositionPlan === "object";
|
|
47
|
+
if (hasPrompt === hasPlan) {
|
|
48
|
+
return { error: "Provide exactly one of `prompt` or `compositionPlan`" };
|
|
49
|
+
}
|
|
50
|
+
const params = {};
|
|
51
|
+
if (hasPrompt) {
|
|
52
|
+
params.prompt = b.prompt.trim();
|
|
53
|
+
if (typeof b.musicLengthMs === "number") params.musicLengthMs = b.musicLengthMs;
|
|
54
|
+
if (typeof b.forceInstrumental === "boolean") params.forceInstrumental = b.forceInstrumental;
|
|
55
|
+
} else {
|
|
56
|
+
params.compositionPlan = b.compositionPlan;
|
|
57
|
+
}
|
|
58
|
+
if (typeof b.modelId === "string" && b.modelId.length > 0) params.modelId = b.modelId;
|
|
59
|
+
if (typeof b.outputFormat === "string" && b.outputFormat.length > 0) {
|
|
60
|
+
params.outputFormat = b.outputFormat;
|
|
61
|
+
}
|
|
62
|
+
return { params };
|
|
63
|
+
}
|
|
42
64
|
function resolveProxyOptions(config) {
|
|
43
65
|
if (config.proxy) {
|
|
44
66
|
if (!config.proxy.runtimeToken || !config.proxy.projectId || !config.proxy.apiUrl) {
|
|
@@ -124,6 +146,34 @@ function createProxyHandlers(proxy, log) {
|
|
|
124
146
|
}
|
|
125
147
|
});
|
|
126
148
|
}
|
|
149
|
+
async function doMusic(req) {
|
|
150
|
+
if (req.method !== "POST") return json({ error: "Method Not Allowed" }, 405);
|
|
151
|
+
const raw = await readJson(req);
|
|
152
|
+
const parsed = parseMusicBody(raw);
|
|
153
|
+
if ("error" in parsed) return json({ error: parsed.error }, 400);
|
|
154
|
+
try {
|
|
155
|
+
const upstream = await fetchImpl(proxyUrl("/api/voice/music", {}), {
|
|
156
|
+
method: "POST",
|
|
157
|
+
headers: {
|
|
158
|
+
"x-runtime-token": proxy.runtimeToken,
|
|
159
|
+
"content-type": "application/json",
|
|
160
|
+
accept: "audio/mpeg"
|
|
161
|
+
},
|
|
162
|
+
body: JSON.stringify(parsed.params),
|
|
163
|
+
credentials: "omit"
|
|
164
|
+
});
|
|
165
|
+
const buf = await upstream.arrayBuffer();
|
|
166
|
+
const headers = {};
|
|
167
|
+
const ct = upstream.headers.get("content-type");
|
|
168
|
+
if (ct) headers["content-type"] = ct;
|
|
169
|
+
return new Response(buf, { status: upstream.status, headers });
|
|
170
|
+
} catch (err) {
|
|
171
|
+
log("error", "proxy music failed", {
|
|
172
|
+
error: err instanceof Error ? err.message : String(err)
|
|
173
|
+
});
|
|
174
|
+
return json({ error: "music proxy failed" }, 502);
|
|
175
|
+
}
|
|
176
|
+
}
|
|
127
177
|
function notImplemented(label) {
|
|
128
178
|
return async () => json(
|
|
129
179
|
{
|
|
@@ -136,6 +186,7 @@ function createProxyHandlers(proxy, log) {
|
|
|
136
186
|
return {
|
|
137
187
|
signedUrl: doSignedUrl,
|
|
138
188
|
tts: notImplemented("tts"),
|
|
189
|
+
music: doMusic,
|
|
139
190
|
agent: {
|
|
140
191
|
create: notImplemented("agent.create"),
|
|
141
192
|
patch: notImplemented("agent.patch"),
|
|
@@ -276,6 +327,25 @@ function createVoiceHandlers(config) {
|
|
|
276
327
|
return handleElError(e, "TTS failed");
|
|
277
328
|
}
|
|
278
329
|
}
|
|
330
|
+
async function doMusic(req) {
|
|
331
|
+
if (req.method !== "POST") return json({ error: "Method Not Allowed" }, 405);
|
|
332
|
+
const u = await resolveUser(req);
|
|
333
|
+
if (u instanceof Response) return u;
|
|
334
|
+
const raw = await readJson(req);
|
|
335
|
+
const parsed = parseMusicBody(raw);
|
|
336
|
+
if ("error" in parsed) return json({ error: parsed.error }, 400);
|
|
337
|
+
try {
|
|
338
|
+
const { audio, contentType } = await el.composeMusic(parsed.params);
|
|
339
|
+
return new Response(audio, {
|
|
340
|
+
headers: {
|
|
341
|
+
"content-type": contentType,
|
|
342
|
+
"cache-control": "private, max-age=3600"
|
|
343
|
+
}
|
|
344
|
+
});
|
|
345
|
+
} catch (e) {
|
|
346
|
+
return handleElError(e, "Music generation failed");
|
|
347
|
+
}
|
|
348
|
+
}
|
|
279
349
|
async function doAgentCreate(req) {
|
|
280
350
|
if (req.method !== "POST") return json({ error: "Method Not Allowed" }, 405);
|
|
281
351
|
const u = await resolveUser(req);
|
|
@@ -406,6 +476,7 @@ function createVoiceHandlers(config) {
|
|
|
406
476
|
return {
|
|
407
477
|
signedUrl: doSignedUrl,
|
|
408
478
|
tts: doTts,
|
|
479
|
+
music: doMusic,
|
|
409
480
|
agent: {
|
|
410
481
|
create: doAgentCreate,
|
|
411
482
|
patch: doAgentPatch,
|
|
@@ -450,4 +521,4 @@ export {
|
|
|
450
521
|
createVoiceHandlers,
|
|
451
522
|
toNodeListener
|
|
452
523
|
};
|
|
453
|
-
//# sourceMappingURL=chunk-
|
|
524
|
+
//# sourceMappingURL=chunk-O362EJQR.js.map
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import {
|
|
2
2
|
createVoiceHandlers
|
|
3
|
-
} from "./chunk-
|
|
3
|
+
} from "./chunk-O362EJQR.js";
|
|
4
4
|
|
|
5
5
|
// src/route/index.ts
|
|
6
6
|
function defaultHandlers() {
|
|
@@ -11,6 +11,7 @@ function createVoiceRoute(config) {
|
|
|
11
11
|
return {
|
|
12
12
|
signedUrl: { GET: (req) => v.signedUrl(req) },
|
|
13
13
|
ttsPreview: { POST: (req) => v.tts(req) },
|
|
14
|
+
music: { POST: (req) => v.music(req) },
|
|
14
15
|
agent: {
|
|
15
16
|
POST: (req) => v.agent.create(req),
|
|
16
17
|
PATCH: (req) => v.agent.patch(req),
|
|
@@ -24,4 +25,4 @@ export {
|
|
|
24
25
|
defaultHandlers,
|
|
25
26
|
createVoiceRoute
|
|
26
27
|
};
|
|
27
|
-
//# sourceMappingURL=chunk-
|
|
28
|
+
//# sourceMappingURL=chunk-T3T4EBWY.js.map
|
package/dist/index.cjs
CHANGED
|
@@ -27,6 +27,7 @@ __export(index_exports, {
|
|
|
27
27
|
DEFAULT_ALLOWED_TAGS: () => DEFAULT_ALLOWED_TAGS,
|
|
28
28
|
DEFAULT_ELEVENLABS_BASE_URL: () => DEFAULT_ELEVENLABS_BASE_URL,
|
|
29
29
|
DEFAULT_MEMORY_BLOCK: () => DEFAULT_MEMORY_BLOCK,
|
|
30
|
+
DEFAULT_MUSIC_MODEL: () => DEFAULT_MUSIC_MODEL,
|
|
30
31
|
DEFAULT_TWILIO_BASE_URL: () => DEFAULT_TWILIO_BASE_URL,
|
|
31
32
|
DEFAULT_VOICE_SETTINGS: () => DEFAULT_VOICE_SETTINGS,
|
|
32
33
|
DirectTelephonyClient: () => DirectTelephonyClient,
|
|
@@ -248,6 +249,7 @@ function resolveConvaiTtsModel(requested) {
|
|
|
248
249
|
if (requested && CONVAI_SUPPORTED_TTS_MODELS.has(requested)) return requested;
|
|
249
250
|
return CONVAI_TTS_MODEL_FALLBACK;
|
|
250
251
|
}
|
|
252
|
+
var DEFAULT_MUSIC_MODEL = "music_v1";
|
|
251
253
|
var ElevenLabsApiError = class extends Error {
|
|
252
254
|
constructor(message, status, body) {
|
|
253
255
|
super(message);
|
|
@@ -448,6 +450,110 @@ var ElevenLabsClient = class {
|
|
|
448
450
|
const contentType = res.headers.get("content-type") ?? accept;
|
|
449
451
|
return { audio, modelId: used, contentType };
|
|
450
452
|
}
|
|
453
|
+
/**
|
|
454
|
+
* Low-level escape hatch for any ElevenLabs endpoint not covered by a
|
|
455
|
+
* dedicated method (music, sound-generation, speech-to-text, dubbing,
|
|
456
|
+
* voice-design, …). Handles auth, query encoding, JSON body encoding,
|
|
457
|
+
* and JSON-vs-binary response parsing; non-2xx responses throw
|
|
458
|
+
* {@link ElevenLabsApiError} like the typed methods do.
|
|
459
|
+
*
|
|
460
|
+
* @example
|
|
461
|
+
* ```ts
|
|
462
|
+
* const { json } = await el.request({ method: 'GET', path: '/v1/models' })
|
|
463
|
+
* const { audio } = await el.request({
|
|
464
|
+
* method: 'POST',
|
|
465
|
+
* path: '/v1/sound-generation',
|
|
466
|
+
* body: { text: 'door creak' },
|
|
467
|
+
* accept: 'audio/mpeg',
|
|
468
|
+
* })
|
|
469
|
+
* ```
|
|
470
|
+
*/
|
|
471
|
+
async request(opts) {
|
|
472
|
+
const accept = opts.accept ?? "application/json";
|
|
473
|
+
let url = `${this.baseUrl}${opts.path.startsWith("/") ? "" : "/"}${opts.path}`;
|
|
474
|
+
if (opts.query) {
|
|
475
|
+
const params = new URLSearchParams();
|
|
476
|
+
for (const [k, v] of Object.entries(opts.query)) {
|
|
477
|
+
if (v !== null && v !== void 0) params.set(k, String(v));
|
|
478
|
+
}
|
|
479
|
+
const qs = params.toString();
|
|
480
|
+
if (qs) url += `${url.includes("?") ? "&" : "?"}${qs}`;
|
|
481
|
+
}
|
|
482
|
+
const headers = this.headers({ accept, ...opts.headers ?? {} });
|
|
483
|
+
const init = { method: opts.method, headers };
|
|
484
|
+
if (opts.body !== void 0 && opts.body !== null) {
|
|
485
|
+
const isPlainPayload = typeof opts.body === "object" && !(opts.body instanceof ArrayBuffer) && !(opts.body instanceof Uint8Array) && !(typeof Blob !== "undefined" && opts.body instanceof Blob) && !(typeof FormData !== "undefined" && opts.body instanceof FormData);
|
|
486
|
+
if (isPlainPayload) {
|
|
487
|
+
headers["content-type"] = "application/json";
|
|
488
|
+
init.body = JSON.stringify(opts.body);
|
|
489
|
+
} else {
|
|
490
|
+
init.body = opts.body;
|
|
491
|
+
}
|
|
492
|
+
}
|
|
493
|
+
const res = await this.fetchImpl(url, init);
|
|
494
|
+
if (!res.ok) {
|
|
495
|
+
const text = await res.text();
|
|
496
|
+
throw new ElevenLabsApiError(
|
|
497
|
+
`request ${opts.method} ${opts.path} failed: ${res.status}`,
|
|
498
|
+
res.status,
|
|
499
|
+
text
|
|
500
|
+
);
|
|
501
|
+
}
|
|
502
|
+
const contentType = res.headers.get("content-type") ?? accept;
|
|
503
|
+
if (contentType.includes("application/json")) {
|
|
504
|
+
return { status: res.status, contentType, json: await res.json() };
|
|
505
|
+
}
|
|
506
|
+
return { status: res.status, contentType, audio: await res.arrayBuffer() };
|
|
507
|
+
}
|
|
508
|
+
/**
|
|
509
|
+
* Generate music via `POST /v1/music`. Provide exactly one of `prompt`
|
|
510
|
+
* or `compositionPlan` (ElevenLabs returns 422 otherwise). Returns the
|
|
511
|
+
* raw audio bytes plus the response content type.
|
|
512
|
+
*
|
|
513
|
+
* @example
|
|
514
|
+
* ```ts
|
|
515
|
+
* const { audio } = await el.composeMusic({
|
|
516
|
+
* prompt: 'Upbeat synthwave with driving bass',
|
|
517
|
+
* musicLengthMs: 30_000,
|
|
518
|
+
* })
|
|
519
|
+
* ```
|
|
520
|
+
*/
|
|
521
|
+
async composeMusic(params) {
|
|
522
|
+
const hasPrompt = typeof params.prompt === "string" && params.prompt.length > 0;
|
|
523
|
+
const hasPlan = params.compositionPlan != null;
|
|
524
|
+
if (hasPrompt === hasPlan) {
|
|
525
|
+
throw new Error(
|
|
526
|
+
"composeMusic: provide exactly one of `prompt` or `compositionPlan`"
|
|
527
|
+
);
|
|
528
|
+
}
|
|
529
|
+
const body = {
|
|
530
|
+
model_id: params.modelId ?? DEFAULT_MUSIC_MODEL
|
|
531
|
+
};
|
|
532
|
+
if (hasPrompt) {
|
|
533
|
+
body.prompt = params.prompt;
|
|
534
|
+
if (params.musicLengthMs !== void 0) body.music_length_ms = params.musicLengthMs;
|
|
535
|
+
if (params.forceInstrumental !== void 0) {
|
|
536
|
+
body.force_instrumental = params.forceInstrumental;
|
|
537
|
+
}
|
|
538
|
+
} else {
|
|
539
|
+
body.composition_plan = params.compositionPlan;
|
|
540
|
+
}
|
|
541
|
+
const { audio, contentType } = await this.request({
|
|
542
|
+
method: "POST",
|
|
543
|
+
path: "/v1/music",
|
|
544
|
+
body,
|
|
545
|
+
accept: "audio/mpeg",
|
|
546
|
+
...params.outputFormat ? { query: { output_format: params.outputFormat } } : {}
|
|
547
|
+
});
|
|
548
|
+
if (!audio) {
|
|
549
|
+
throw new ElevenLabsApiError(
|
|
550
|
+
"composeMusic: expected audio bytes but got a JSON response",
|
|
551
|
+
500,
|
|
552
|
+
""
|
|
553
|
+
);
|
|
554
|
+
}
|
|
555
|
+
return { audio, contentType };
|
|
556
|
+
}
|
|
451
557
|
/** Probe whether a given voice id still exists on the caller's account. */
|
|
452
558
|
async voiceExists(voiceId) {
|
|
453
559
|
const res = await this.fetchImpl(
|
|
@@ -1280,6 +1386,7 @@ function isVoiceMockEnv() {
|
|
|
1280
1386
|
DEFAULT_ALLOWED_TAGS,
|
|
1281
1387
|
DEFAULT_ELEVENLABS_BASE_URL,
|
|
1282
1388
|
DEFAULT_MEMORY_BLOCK,
|
|
1389
|
+
DEFAULT_MUSIC_MODEL,
|
|
1283
1390
|
DEFAULT_TWILIO_BASE_URL,
|
|
1284
1391
|
DEFAULT_VOICE_SETTINGS,
|
|
1285
1392
|
DirectTelephonyClient,
|
package/dist/index.d.cts
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
|
-
import { E as Expressivity } from './types-
|
|
2
|
-
export { A as AUDIO_TAGS, a as AUDIO_TAG_GROUPS, b as AudioTag, c as AudioTagGroup, C as CONVAI_SUPPORTED_TTS_MODELS, d as CONVAI_TTS_MODEL_FALLBACK, e as Companion, f as CompanionStore, g as
|
|
1
|
+
import { E as Expressivity } from './types-W9y6eMj3.cjs';
|
|
2
|
+
export { A as AUDIO_TAGS, a as AUDIO_TAG_GROUPS, b as AudioTag, c as AudioTagGroup, C as CONVAI_SUPPORTED_TTS_MODELS, d as CONVAI_TTS_MODEL_FALLBACK, e as Companion, f as CompanionStore, g as ComposeMusicParams, h as ConvaiClientTool, i as CreateAgentParams, j as CreateCompanionBody, D as DEFAULT_ALLOWED_TAGS, k as DEFAULT_ELEVENLABS_BASE_URL, l as DEFAULT_MUSIC_MODEL, m as DEFAULT_VOICE_SETTINGS, n as EXPRESSIVITY_BLOCK_CLOSE, o as EXPRESSIVITY_BLOCK_OPEN, p as EXPRESSIVITY_OPTIONS, q as ElevenLabsApiError, r as ElevenLabsClient, s as ElevenLabsClientConfig, t as ElevenLabsRequestOptions, u as ElevenLabsResponse, M as MEMORY_CLIENT_TOOLS, P as PatchAgentParams, v as PatchCompanionBody, T as TextToSpeechParams, w as TtsPreviewBody, V as VoiceMemoryStore, x as VoiceSettings, y as VoiceUser, z as buildPreviewLine, B as composeExpressivityBlock, F as normalizeAudioTags, G as normalizeExpressivity, H as normalizeVoiceSettings, I as readAudioTags, J as readExpressivity, K as readVoiceSettings, L as resolveConvaiTtsModel, N as stripAudioTags, O as stripExpressivityBlock } from './types-W9y6eMj3.cjs';
|
|
3
3
|
|
|
4
4
|
/**
|
|
5
5
|
* System-prompt composer for the voice agent.
|
package/dist/index.d.ts
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
|
-
import { E as Expressivity } from './types-
|
|
2
|
-
export { A as AUDIO_TAGS, a as AUDIO_TAG_GROUPS, b as AudioTag, c as AudioTagGroup, C as CONVAI_SUPPORTED_TTS_MODELS, d as CONVAI_TTS_MODEL_FALLBACK, e as Companion, f as CompanionStore, g as
|
|
1
|
+
import { E as Expressivity } from './types-W9y6eMj3.js';
|
|
2
|
+
export { A as AUDIO_TAGS, a as AUDIO_TAG_GROUPS, b as AudioTag, c as AudioTagGroup, C as CONVAI_SUPPORTED_TTS_MODELS, d as CONVAI_TTS_MODEL_FALLBACK, e as Companion, f as CompanionStore, g as ComposeMusicParams, h as ConvaiClientTool, i as CreateAgentParams, j as CreateCompanionBody, D as DEFAULT_ALLOWED_TAGS, k as DEFAULT_ELEVENLABS_BASE_URL, l as DEFAULT_MUSIC_MODEL, m as DEFAULT_VOICE_SETTINGS, n as EXPRESSIVITY_BLOCK_CLOSE, o as EXPRESSIVITY_BLOCK_OPEN, p as EXPRESSIVITY_OPTIONS, q as ElevenLabsApiError, r as ElevenLabsClient, s as ElevenLabsClientConfig, t as ElevenLabsRequestOptions, u as ElevenLabsResponse, M as MEMORY_CLIENT_TOOLS, P as PatchAgentParams, v as PatchCompanionBody, T as TextToSpeechParams, w as TtsPreviewBody, V as VoiceMemoryStore, x as VoiceSettings, y as VoiceUser, z as buildPreviewLine, B as composeExpressivityBlock, F as normalizeAudioTags, G as normalizeExpressivity, H as normalizeVoiceSettings, I as readAudioTags, J as readExpressivity, K as readVoiceSettings, L as resolveConvaiTtsModel, N as stripAudioTags, O as stripExpressivityBlock } from './types-W9y6eMj3.js';
|
|
3
3
|
|
|
4
4
|
/**
|
|
5
5
|
* System-prompt composer for the voice agent.
|
package/dist/index.js
CHANGED
|
@@ -3,6 +3,7 @@ import {
|
|
|
3
3
|
CONVAI_TTS_MODEL_FALLBACK,
|
|
4
4
|
DEFAULT_ELEVENLABS_BASE_URL,
|
|
5
5
|
DEFAULT_MEMORY_BLOCK,
|
|
6
|
+
DEFAULT_MUSIC_MODEL,
|
|
6
7
|
ElevenLabsApiError,
|
|
7
8
|
ElevenLabsClient,
|
|
8
9
|
MEMORY_CLIENT_TOOLS,
|
|
@@ -10,7 +11,7 @@ import {
|
|
|
10
11
|
extractBasePrompt,
|
|
11
12
|
resolveConvaiTtsModel,
|
|
12
13
|
stripMemoryBlock
|
|
13
|
-
} from "./chunk-
|
|
14
|
+
} from "./chunk-B4JC3DK2.js";
|
|
14
15
|
import {
|
|
15
16
|
AUDIO_TAGS,
|
|
16
17
|
AUDIO_TAG_GROUPS,
|
|
@@ -699,6 +700,7 @@ export {
|
|
|
699
700
|
DEFAULT_ALLOWED_TAGS,
|
|
700
701
|
DEFAULT_ELEVENLABS_BASE_URL,
|
|
701
702
|
DEFAULT_MEMORY_BLOCK,
|
|
703
|
+
DEFAULT_MUSIC_MODEL,
|
|
702
704
|
DEFAULT_TWILIO_BASE_URL,
|
|
703
705
|
DEFAULT_VOICE_SETTINGS,
|
|
704
706
|
DirectTelephonyClient,
|
package/dist/route/agent.cjs
CHANGED
|
@@ -205,6 +205,7 @@ function resolveConvaiTtsModel(requested) {
|
|
|
205
205
|
if (requested && CONVAI_SUPPORTED_TTS_MODELS.has(requested)) return requested;
|
|
206
206
|
return CONVAI_TTS_MODEL_FALLBACK;
|
|
207
207
|
}
|
|
208
|
+
var DEFAULT_MUSIC_MODEL = "music_v1";
|
|
208
209
|
var ElevenLabsApiError = class extends Error {
|
|
209
210
|
constructor(message, status, body) {
|
|
210
211
|
super(message);
|
|
@@ -405,6 +406,110 @@ var ElevenLabsClient = class {
|
|
|
405
406
|
const contentType = res.headers.get("content-type") ?? accept;
|
|
406
407
|
return { audio, modelId: used, contentType };
|
|
407
408
|
}
|
|
409
|
+
/**
|
|
410
|
+
* Low-level escape hatch for any ElevenLabs endpoint not covered by a
|
|
411
|
+
* dedicated method (music, sound-generation, speech-to-text, dubbing,
|
|
412
|
+
* voice-design, …). Handles auth, query encoding, JSON body encoding,
|
|
413
|
+
* and JSON-vs-binary response parsing; non-2xx responses throw
|
|
414
|
+
* {@link ElevenLabsApiError} like the typed methods do.
|
|
415
|
+
*
|
|
416
|
+
* @example
|
|
417
|
+
* ```ts
|
|
418
|
+
* const { json } = await el.request({ method: 'GET', path: '/v1/models' })
|
|
419
|
+
* const { audio } = await el.request({
|
|
420
|
+
* method: 'POST',
|
|
421
|
+
* path: '/v1/sound-generation',
|
|
422
|
+
* body: { text: 'door creak' },
|
|
423
|
+
* accept: 'audio/mpeg',
|
|
424
|
+
* })
|
|
425
|
+
* ```
|
|
426
|
+
*/
|
|
427
|
+
async request(opts) {
|
|
428
|
+
const accept = opts.accept ?? "application/json";
|
|
429
|
+
let url = `${this.baseUrl}${opts.path.startsWith("/") ? "" : "/"}${opts.path}`;
|
|
430
|
+
if (opts.query) {
|
|
431
|
+
const params = new URLSearchParams();
|
|
432
|
+
for (const [k, v] of Object.entries(opts.query)) {
|
|
433
|
+
if (v !== null && v !== void 0) params.set(k, String(v));
|
|
434
|
+
}
|
|
435
|
+
const qs = params.toString();
|
|
436
|
+
if (qs) url += `${url.includes("?") ? "&" : "?"}${qs}`;
|
|
437
|
+
}
|
|
438
|
+
const headers = this.headers({ accept, ...opts.headers ?? {} });
|
|
439
|
+
const init = { method: opts.method, headers };
|
|
440
|
+
if (opts.body !== void 0 && opts.body !== null) {
|
|
441
|
+
const isPlainPayload = typeof opts.body === "object" && !(opts.body instanceof ArrayBuffer) && !(opts.body instanceof Uint8Array) && !(typeof Blob !== "undefined" && opts.body instanceof Blob) && !(typeof FormData !== "undefined" && opts.body instanceof FormData);
|
|
442
|
+
if (isPlainPayload) {
|
|
443
|
+
headers["content-type"] = "application/json";
|
|
444
|
+
init.body = JSON.stringify(opts.body);
|
|
445
|
+
} else {
|
|
446
|
+
init.body = opts.body;
|
|
447
|
+
}
|
|
448
|
+
}
|
|
449
|
+
const res = await this.fetchImpl(url, init);
|
|
450
|
+
if (!res.ok) {
|
|
451
|
+
const text = await res.text();
|
|
452
|
+
throw new ElevenLabsApiError(
|
|
453
|
+
`request ${opts.method} ${opts.path} failed: ${res.status}`,
|
|
454
|
+
res.status,
|
|
455
|
+
text
|
|
456
|
+
);
|
|
457
|
+
}
|
|
458
|
+
const contentType = res.headers.get("content-type") ?? accept;
|
|
459
|
+
if (contentType.includes("application/json")) {
|
|
460
|
+
return { status: res.status, contentType, json: await res.json() };
|
|
461
|
+
}
|
|
462
|
+
return { status: res.status, contentType, audio: await res.arrayBuffer() };
|
|
463
|
+
}
|
|
464
|
+
/**
|
|
465
|
+
* Generate music via `POST /v1/music`. Provide exactly one of `prompt`
|
|
466
|
+
* or `compositionPlan` (ElevenLabs returns 422 otherwise). Returns the
|
|
467
|
+
* raw audio bytes plus the response content type.
|
|
468
|
+
*
|
|
469
|
+
* @example
|
|
470
|
+
* ```ts
|
|
471
|
+
* const { audio } = await el.composeMusic({
|
|
472
|
+
* prompt: 'Upbeat synthwave with driving bass',
|
|
473
|
+
* musicLengthMs: 30_000,
|
|
474
|
+
* })
|
|
475
|
+
* ```
|
|
476
|
+
*/
|
|
477
|
+
async composeMusic(params) {
|
|
478
|
+
const hasPrompt = typeof params.prompt === "string" && params.prompt.length > 0;
|
|
479
|
+
const hasPlan = params.compositionPlan != null;
|
|
480
|
+
if (hasPrompt === hasPlan) {
|
|
481
|
+
throw new Error(
|
|
482
|
+
"composeMusic: provide exactly one of `prompt` or `compositionPlan`"
|
|
483
|
+
);
|
|
484
|
+
}
|
|
485
|
+
const body = {
|
|
486
|
+
model_id: params.modelId ?? DEFAULT_MUSIC_MODEL
|
|
487
|
+
};
|
|
488
|
+
if (hasPrompt) {
|
|
489
|
+
body.prompt = params.prompt;
|
|
490
|
+
if (params.musicLengthMs !== void 0) body.music_length_ms = params.musicLengthMs;
|
|
491
|
+
if (params.forceInstrumental !== void 0) {
|
|
492
|
+
body.force_instrumental = params.forceInstrumental;
|
|
493
|
+
}
|
|
494
|
+
} else {
|
|
495
|
+
body.composition_plan = params.compositionPlan;
|
|
496
|
+
}
|
|
497
|
+
const { audio, contentType } = await this.request({
|
|
498
|
+
method: "POST",
|
|
499
|
+
path: "/v1/music",
|
|
500
|
+
body,
|
|
501
|
+
accept: "audio/mpeg",
|
|
502
|
+
...params.outputFormat ? { query: { output_format: params.outputFormat } } : {}
|
|
503
|
+
});
|
|
504
|
+
if (!audio) {
|
|
505
|
+
throw new ElevenLabsApiError(
|
|
506
|
+
"composeMusic: expected audio bytes but got a JSON response",
|
|
507
|
+
500,
|
|
508
|
+
""
|
|
509
|
+
);
|
|
510
|
+
}
|
|
511
|
+
return { audio, contentType };
|
|
512
|
+
}
|
|
408
513
|
/** Probe whether a given voice id still exists on the caller's account. */
|
|
409
514
|
async voiceExists(voiceId) {
|
|
410
515
|
const res = await this.fetchImpl(
|
|
@@ -586,6 +691,28 @@ async function readJson(req) {
|
|
|
586
691
|
}
|
|
587
692
|
var NOOP_LOGGER = () => {
|
|
588
693
|
};
|
|
694
|
+
function parseMusicBody(raw) {
|
|
695
|
+
if (raw === null || typeof raw !== "object") return { error: "Invalid JSON body" };
|
|
696
|
+
const b = raw;
|
|
697
|
+
const hasPrompt = typeof b.prompt === "string" && b.prompt.trim().length > 0;
|
|
698
|
+
const hasPlan = b.compositionPlan != null && typeof b.compositionPlan === "object";
|
|
699
|
+
if (hasPrompt === hasPlan) {
|
|
700
|
+
return { error: "Provide exactly one of `prompt` or `compositionPlan`" };
|
|
701
|
+
}
|
|
702
|
+
const params = {};
|
|
703
|
+
if (hasPrompt) {
|
|
704
|
+
params.prompt = b.prompt.trim();
|
|
705
|
+
if (typeof b.musicLengthMs === "number") params.musicLengthMs = b.musicLengthMs;
|
|
706
|
+
if (typeof b.forceInstrumental === "boolean") params.forceInstrumental = b.forceInstrumental;
|
|
707
|
+
} else {
|
|
708
|
+
params.compositionPlan = b.compositionPlan;
|
|
709
|
+
}
|
|
710
|
+
if (typeof b.modelId === "string" && b.modelId.length > 0) params.modelId = b.modelId;
|
|
711
|
+
if (typeof b.outputFormat === "string" && b.outputFormat.length > 0) {
|
|
712
|
+
params.outputFormat = b.outputFormat;
|
|
713
|
+
}
|
|
714
|
+
return { params };
|
|
715
|
+
}
|
|
589
716
|
function resolveProxyOptions(config) {
|
|
590
717
|
if (config.proxy) {
|
|
591
718
|
if (!config.proxy.runtimeToken || !config.proxy.projectId || !config.proxy.apiUrl) {
|
|
@@ -671,6 +798,34 @@ function createProxyHandlers(proxy, log) {
|
|
|
671
798
|
}
|
|
672
799
|
});
|
|
673
800
|
}
|
|
801
|
+
async function doMusic(req) {
|
|
802
|
+
if (req.method !== "POST") return json({ error: "Method Not Allowed" }, 405);
|
|
803
|
+
const raw = await readJson(req);
|
|
804
|
+
const parsed = parseMusicBody(raw);
|
|
805
|
+
if ("error" in parsed) return json({ error: parsed.error }, 400);
|
|
806
|
+
try {
|
|
807
|
+
const upstream = await fetchImpl(proxyUrl("/api/voice/music", {}), {
|
|
808
|
+
method: "POST",
|
|
809
|
+
headers: {
|
|
810
|
+
"x-runtime-token": proxy.runtimeToken,
|
|
811
|
+
"content-type": "application/json",
|
|
812
|
+
accept: "audio/mpeg"
|
|
813
|
+
},
|
|
814
|
+
body: JSON.stringify(parsed.params),
|
|
815
|
+
credentials: "omit"
|
|
816
|
+
});
|
|
817
|
+
const buf = await upstream.arrayBuffer();
|
|
818
|
+
const headers = {};
|
|
819
|
+
const ct = upstream.headers.get("content-type");
|
|
820
|
+
if (ct) headers["content-type"] = ct;
|
|
821
|
+
return new Response(buf, { status: upstream.status, headers });
|
|
822
|
+
} catch (err) {
|
|
823
|
+
log("error", "proxy music failed", {
|
|
824
|
+
error: err instanceof Error ? err.message : String(err)
|
|
825
|
+
});
|
|
826
|
+
return json({ error: "music proxy failed" }, 502);
|
|
827
|
+
}
|
|
828
|
+
}
|
|
674
829
|
function notImplemented(label) {
|
|
675
830
|
return async () => json(
|
|
676
831
|
{
|
|
@@ -683,6 +838,7 @@ function createProxyHandlers(proxy, log) {
|
|
|
683
838
|
return {
|
|
684
839
|
signedUrl: doSignedUrl,
|
|
685
840
|
tts: notImplemented("tts"),
|
|
841
|
+
music: doMusic,
|
|
686
842
|
agent: {
|
|
687
843
|
create: notImplemented("agent.create"),
|
|
688
844
|
patch: notImplemented("agent.patch"),
|
|
@@ -823,6 +979,25 @@ function createVoiceHandlers(config) {
|
|
|
823
979
|
return handleElError(e, "TTS failed");
|
|
824
980
|
}
|
|
825
981
|
}
|
|
982
|
+
async function doMusic(req) {
|
|
983
|
+
if (req.method !== "POST") return json({ error: "Method Not Allowed" }, 405);
|
|
984
|
+
const u = await resolveUser(req);
|
|
985
|
+
if (u instanceof Response) return u;
|
|
986
|
+
const raw = await readJson(req);
|
|
987
|
+
const parsed = parseMusicBody(raw);
|
|
988
|
+
if ("error" in parsed) return json({ error: parsed.error }, 400);
|
|
989
|
+
try {
|
|
990
|
+
const { audio, contentType } = await el.composeMusic(parsed.params);
|
|
991
|
+
return new Response(audio, {
|
|
992
|
+
headers: {
|
|
993
|
+
"content-type": contentType,
|
|
994
|
+
"cache-control": "private, max-age=3600"
|
|
995
|
+
}
|
|
996
|
+
});
|
|
997
|
+
} catch (e) {
|
|
998
|
+
return handleElError(e, "Music generation failed");
|
|
999
|
+
}
|
|
1000
|
+
}
|
|
826
1001
|
async function doAgentCreate(req) {
|
|
827
1002
|
if (req.method !== "POST") return json({ error: "Method Not Allowed" }, 405);
|
|
828
1003
|
const u = await resolveUser(req);
|
|
@@ -953,6 +1128,7 @@ function createVoiceHandlers(config) {
|
|
|
953
1128
|
return {
|
|
954
1129
|
signedUrl: doSignedUrl,
|
|
955
1130
|
tts: doTts,
|
|
1131
|
+
music: doMusic,
|
|
956
1132
|
agent: {
|
|
957
1133
|
create: doAgentCreate,
|
|
958
1134
|
patch: doAgentPatch,
|