@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
|
@@ -0,0 +1,22 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Drop-in `POST /voice/music` handler — generate music via the
|
|
3
|
+
* ElevenLabs Music API. Returns raw audio bytes.
|
|
4
|
+
*
|
|
5
|
+
* Unlike companion CRUD, music generation is project-scoped (not
|
|
6
|
+
* per-end-user), so it works in BOTH BYO-EL and runtime-token proxy
|
|
7
|
+
* mode. In proxy mode it forwards to the Shogo API, which holds the
|
|
8
|
+
* pooled EL key.
|
|
9
|
+
*
|
|
10
|
+
* Body mirrors `ComposeMusicParams`: provide exactly one of `prompt`
|
|
11
|
+
* or `compositionPlan`, plus optional `musicLengthMs`, `modelId`,
|
|
12
|
+
* `forceInstrumental`, `outputFormat`.
|
|
13
|
+
*
|
|
14
|
+
* @example
|
|
15
|
+
* ```ts
|
|
16
|
+
* // app/api/voice/music+api.ts (Expo Router)
|
|
17
|
+
* export { POST } from '@shogo-ai/sdk/voice/route/music'
|
|
18
|
+
* ```
|
|
19
|
+
*/
|
|
20
|
+
declare const POST: (req: Request) => Promise<Response>;
|
|
21
|
+
|
|
22
|
+
export { POST };
|
|
@@ -0,0 +1,22 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Drop-in `POST /voice/music` handler — generate music via the
|
|
3
|
+
* ElevenLabs Music API. Returns raw audio bytes.
|
|
4
|
+
*
|
|
5
|
+
* Unlike companion CRUD, music generation is project-scoped (not
|
|
6
|
+
* per-end-user), so it works in BOTH BYO-EL and runtime-token proxy
|
|
7
|
+
* mode. In proxy mode it forwards to the Shogo API, which holds the
|
|
8
|
+
* pooled EL key.
|
|
9
|
+
*
|
|
10
|
+
* Body mirrors `ComposeMusicParams`: provide exactly one of `prompt`
|
|
11
|
+
* or `compositionPlan`, plus optional `musicLengthMs`, `modelId`,
|
|
12
|
+
* `forceInstrumental`, `outputFormat`.
|
|
13
|
+
*
|
|
14
|
+
* @example
|
|
15
|
+
* ```ts
|
|
16
|
+
* // app/api/voice/music+api.ts (Expo Router)
|
|
17
|
+
* export { POST } from '@shogo-ai/sdk/voice/route/music'
|
|
18
|
+
* ```
|
|
19
|
+
*/
|
|
20
|
+
declare const POST: (req: Request) => Promise<Response>;
|
|
21
|
+
|
|
22
|
+
export { POST };
|
|
@@ -0,0 +1,13 @@
|
|
|
1
|
+
import {
|
|
2
|
+
defaultHandlers
|
|
3
|
+
} from "../chunk-T3T4EBWY.js";
|
|
4
|
+
import "../chunk-O362EJQR.js";
|
|
5
|
+
import "../chunk-B4JC3DK2.js";
|
|
6
|
+
import "../chunk-Q3TJAJ54.js";
|
|
7
|
+
|
|
8
|
+
// src/route/music.ts
|
|
9
|
+
var POST = (req) => defaultHandlers().music(req);
|
|
10
|
+
export {
|
|
11
|
+
POST
|
|
12
|
+
};
|
|
13
|
+
//# sourceMappingURL=music.js.map
|
|
@@ -203,6 +203,7 @@ function resolveConvaiTtsModel(requested) {
|
|
|
203
203
|
if (requested && CONVAI_SUPPORTED_TTS_MODELS.has(requested)) return requested;
|
|
204
204
|
return CONVAI_TTS_MODEL_FALLBACK;
|
|
205
205
|
}
|
|
206
|
+
var DEFAULT_MUSIC_MODEL = "music_v1";
|
|
206
207
|
var ElevenLabsApiError = class extends Error {
|
|
207
208
|
constructor(message, status, body) {
|
|
208
209
|
super(message);
|
|
@@ -403,6 +404,110 @@ var ElevenLabsClient = class {
|
|
|
403
404
|
const contentType = res.headers.get("content-type") ?? accept;
|
|
404
405
|
return { audio, modelId: used, contentType };
|
|
405
406
|
}
|
|
407
|
+
/**
|
|
408
|
+
* Low-level escape hatch for any ElevenLabs endpoint not covered by a
|
|
409
|
+
* dedicated method (music, sound-generation, speech-to-text, dubbing,
|
|
410
|
+
* voice-design, …). Handles auth, query encoding, JSON body encoding,
|
|
411
|
+
* and JSON-vs-binary response parsing; non-2xx responses throw
|
|
412
|
+
* {@link ElevenLabsApiError} like the typed methods do.
|
|
413
|
+
*
|
|
414
|
+
* @example
|
|
415
|
+
* ```ts
|
|
416
|
+
* const { json } = await el.request({ method: 'GET', path: '/v1/models' })
|
|
417
|
+
* const { audio } = await el.request({
|
|
418
|
+
* method: 'POST',
|
|
419
|
+
* path: '/v1/sound-generation',
|
|
420
|
+
* body: { text: 'door creak' },
|
|
421
|
+
* accept: 'audio/mpeg',
|
|
422
|
+
* })
|
|
423
|
+
* ```
|
|
424
|
+
*/
|
|
425
|
+
async request(opts) {
|
|
426
|
+
const accept = opts.accept ?? "application/json";
|
|
427
|
+
let url = `${this.baseUrl}${opts.path.startsWith("/") ? "" : "/"}${opts.path}`;
|
|
428
|
+
if (opts.query) {
|
|
429
|
+
const params = new URLSearchParams();
|
|
430
|
+
for (const [k, v] of Object.entries(opts.query)) {
|
|
431
|
+
if (v !== null && v !== void 0) params.set(k, String(v));
|
|
432
|
+
}
|
|
433
|
+
const qs = params.toString();
|
|
434
|
+
if (qs) url += `${url.includes("?") ? "&" : "?"}${qs}`;
|
|
435
|
+
}
|
|
436
|
+
const headers = this.headers({ accept, ...opts.headers ?? {} });
|
|
437
|
+
const init = { method: opts.method, headers };
|
|
438
|
+
if (opts.body !== void 0 && opts.body !== null) {
|
|
439
|
+
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);
|
|
440
|
+
if (isPlainPayload) {
|
|
441
|
+
headers["content-type"] = "application/json";
|
|
442
|
+
init.body = JSON.stringify(opts.body);
|
|
443
|
+
} else {
|
|
444
|
+
init.body = opts.body;
|
|
445
|
+
}
|
|
446
|
+
}
|
|
447
|
+
const res = await this.fetchImpl(url, init);
|
|
448
|
+
if (!res.ok) {
|
|
449
|
+
const text = await res.text();
|
|
450
|
+
throw new ElevenLabsApiError(
|
|
451
|
+
`request ${opts.method} ${opts.path} failed: ${res.status}`,
|
|
452
|
+
res.status,
|
|
453
|
+
text
|
|
454
|
+
);
|
|
455
|
+
}
|
|
456
|
+
const contentType = res.headers.get("content-type") ?? accept;
|
|
457
|
+
if (contentType.includes("application/json")) {
|
|
458
|
+
return { status: res.status, contentType, json: await res.json() };
|
|
459
|
+
}
|
|
460
|
+
return { status: res.status, contentType, audio: await res.arrayBuffer() };
|
|
461
|
+
}
|
|
462
|
+
/**
|
|
463
|
+
* Generate music via `POST /v1/music`. Provide exactly one of `prompt`
|
|
464
|
+
* or `compositionPlan` (ElevenLabs returns 422 otherwise). Returns the
|
|
465
|
+
* raw audio bytes plus the response content type.
|
|
466
|
+
*
|
|
467
|
+
* @example
|
|
468
|
+
* ```ts
|
|
469
|
+
* const { audio } = await el.composeMusic({
|
|
470
|
+
* prompt: 'Upbeat synthwave with driving bass',
|
|
471
|
+
* musicLengthMs: 30_000,
|
|
472
|
+
* })
|
|
473
|
+
* ```
|
|
474
|
+
*/
|
|
475
|
+
async composeMusic(params) {
|
|
476
|
+
const hasPrompt = typeof params.prompt === "string" && params.prompt.length > 0;
|
|
477
|
+
const hasPlan = params.compositionPlan != null;
|
|
478
|
+
if (hasPrompt === hasPlan) {
|
|
479
|
+
throw new Error(
|
|
480
|
+
"composeMusic: provide exactly one of `prompt` or `compositionPlan`"
|
|
481
|
+
);
|
|
482
|
+
}
|
|
483
|
+
const body = {
|
|
484
|
+
model_id: params.modelId ?? DEFAULT_MUSIC_MODEL
|
|
485
|
+
};
|
|
486
|
+
if (hasPrompt) {
|
|
487
|
+
body.prompt = params.prompt;
|
|
488
|
+
if (params.musicLengthMs !== void 0) body.music_length_ms = params.musicLengthMs;
|
|
489
|
+
if (params.forceInstrumental !== void 0) {
|
|
490
|
+
body.force_instrumental = params.forceInstrumental;
|
|
491
|
+
}
|
|
492
|
+
} else {
|
|
493
|
+
body.composition_plan = params.compositionPlan;
|
|
494
|
+
}
|
|
495
|
+
const { audio, contentType } = await this.request({
|
|
496
|
+
method: "POST",
|
|
497
|
+
path: "/v1/music",
|
|
498
|
+
body,
|
|
499
|
+
accept: "audio/mpeg",
|
|
500
|
+
...params.outputFormat ? { query: { output_format: params.outputFormat } } : {}
|
|
501
|
+
});
|
|
502
|
+
if (!audio) {
|
|
503
|
+
throw new ElevenLabsApiError(
|
|
504
|
+
"composeMusic: expected audio bytes but got a JSON response",
|
|
505
|
+
500,
|
|
506
|
+
""
|
|
507
|
+
);
|
|
508
|
+
}
|
|
509
|
+
return { audio, contentType };
|
|
510
|
+
}
|
|
406
511
|
/** Probe whether a given voice id still exists on the caller's account. */
|
|
407
512
|
async voiceExists(voiceId) {
|
|
408
513
|
const res = await this.fetchImpl(
|
|
@@ -584,6 +689,28 @@ async function readJson(req) {
|
|
|
584
689
|
}
|
|
585
690
|
var NOOP_LOGGER = () => {
|
|
586
691
|
};
|
|
692
|
+
function parseMusicBody(raw) {
|
|
693
|
+
if (raw === null || typeof raw !== "object") return { error: "Invalid JSON body" };
|
|
694
|
+
const b = raw;
|
|
695
|
+
const hasPrompt = typeof b.prompt === "string" && b.prompt.trim().length > 0;
|
|
696
|
+
const hasPlan = b.compositionPlan != null && typeof b.compositionPlan === "object";
|
|
697
|
+
if (hasPrompt === hasPlan) {
|
|
698
|
+
return { error: "Provide exactly one of `prompt` or `compositionPlan`" };
|
|
699
|
+
}
|
|
700
|
+
const params = {};
|
|
701
|
+
if (hasPrompt) {
|
|
702
|
+
params.prompt = b.prompt.trim();
|
|
703
|
+
if (typeof b.musicLengthMs === "number") params.musicLengthMs = b.musicLengthMs;
|
|
704
|
+
if (typeof b.forceInstrumental === "boolean") params.forceInstrumental = b.forceInstrumental;
|
|
705
|
+
} else {
|
|
706
|
+
params.compositionPlan = b.compositionPlan;
|
|
707
|
+
}
|
|
708
|
+
if (typeof b.modelId === "string" && b.modelId.length > 0) params.modelId = b.modelId;
|
|
709
|
+
if (typeof b.outputFormat === "string" && b.outputFormat.length > 0) {
|
|
710
|
+
params.outputFormat = b.outputFormat;
|
|
711
|
+
}
|
|
712
|
+
return { params };
|
|
713
|
+
}
|
|
587
714
|
function resolveProxyOptions(config) {
|
|
588
715
|
if (config.proxy) {
|
|
589
716
|
if (!config.proxy.runtimeToken || !config.proxy.projectId || !config.proxy.apiUrl) {
|
|
@@ -669,6 +796,34 @@ function createProxyHandlers(proxy, log) {
|
|
|
669
796
|
}
|
|
670
797
|
});
|
|
671
798
|
}
|
|
799
|
+
async function doMusic(req) {
|
|
800
|
+
if (req.method !== "POST") return json({ error: "Method Not Allowed" }, 405);
|
|
801
|
+
const raw = await readJson(req);
|
|
802
|
+
const parsed = parseMusicBody(raw);
|
|
803
|
+
if ("error" in parsed) return json({ error: parsed.error }, 400);
|
|
804
|
+
try {
|
|
805
|
+
const upstream = await fetchImpl(proxyUrl("/api/voice/music", {}), {
|
|
806
|
+
method: "POST",
|
|
807
|
+
headers: {
|
|
808
|
+
"x-runtime-token": proxy.runtimeToken,
|
|
809
|
+
"content-type": "application/json",
|
|
810
|
+
accept: "audio/mpeg"
|
|
811
|
+
},
|
|
812
|
+
body: JSON.stringify(parsed.params),
|
|
813
|
+
credentials: "omit"
|
|
814
|
+
});
|
|
815
|
+
const buf = await upstream.arrayBuffer();
|
|
816
|
+
const headers = {};
|
|
817
|
+
const ct = upstream.headers.get("content-type");
|
|
818
|
+
if (ct) headers["content-type"] = ct;
|
|
819
|
+
return new Response(buf, { status: upstream.status, headers });
|
|
820
|
+
} catch (err) {
|
|
821
|
+
log("error", "proxy music failed", {
|
|
822
|
+
error: err instanceof Error ? err.message : String(err)
|
|
823
|
+
});
|
|
824
|
+
return json({ error: "music proxy failed" }, 502);
|
|
825
|
+
}
|
|
826
|
+
}
|
|
672
827
|
function notImplemented(label) {
|
|
673
828
|
return async () => json(
|
|
674
829
|
{
|
|
@@ -681,6 +836,7 @@ function createProxyHandlers(proxy, log) {
|
|
|
681
836
|
return {
|
|
682
837
|
signedUrl: doSignedUrl,
|
|
683
838
|
tts: notImplemented("tts"),
|
|
839
|
+
music: doMusic,
|
|
684
840
|
agent: {
|
|
685
841
|
create: notImplemented("agent.create"),
|
|
686
842
|
patch: notImplemented("agent.patch"),
|
|
@@ -821,6 +977,25 @@ function createVoiceHandlers(config) {
|
|
|
821
977
|
return handleElError(e, "TTS failed");
|
|
822
978
|
}
|
|
823
979
|
}
|
|
980
|
+
async function doMusic(req) {
|
|
981
|
+
if (req.method !== "POST") return json({ error: "Method Not Allowed" }, 405);
|
|
982
|
+
const u = await resolveUser(req);
|
|
983
|
+
if (u instanceof Response) return u;
|
|
984
|
+
const raw = await readJson(req);
|
|
985
|
+
const parsed = parseMusicBody(raw);
|
|
986
|
+
if ("error" in parsed) return json({ error: parsed.error }, 400);
|
|
987
|
+
try {
|
|
988
|
+
const { audio, contentType } = await el.composeMusic(parsed.params);
|
|
989
|
+
return new Response(audio, {
|
|
990
|
+
headers: {
|
|
991
|
+
"content-type": contentType,
|
|
992
|
+
"cache-control": "private, max-age=3600"
|
|
993
|
+
}
|
|
994
|
+
});
|
|
995
|
+
} catch (e) {
|
|
996
|
+
return handleElError(e, "Music generation failed");
|
|
997
|
+
}
|
|
998
|
+
}
|
|
824
999
|
async function doAgentCreate(req) {
|
|
825
1000
|
if (req.method !== "POST") return json({ error: "Method Not Allowed" }, 405);
|
|
826
1001
|
const u = await resolveUser(req);
|
|
@@ -951,6 +1126,7 @@ function createVoiceHandlers(config) {
|
|
|
951
1126
|
return {
|
|
952
1127
|
signedUrl: doSignedUrl,
|
|
953
1128
|
tts: doTts,
|
|
1129
|
+
music: doMusic,
|
|
954
1130
|
agent: {
|
|
955
1131
|
create: doAgentCreate,
|
|
956
1132
|
patch: doAgentPatch,
|
package/dist/route/signed-url.js
CHANGED
|
@@ -1,8 +1,8 @@
|
|
|
1
1
|
import {
|
|
2
2
|
defaultHandlers
|
|
3
|
-
} from "../chunk-
|
|
4
|
-
import "../chunk-
|
|
5
|
-
import "../chunk-
|
|
3
|
+
} from "../chunk-T3T4EBWY.js";
|
|
4
|
+
import "../chunk-O362EJQR.js";
|
|
5
|
+
import "../chunk-B4JC3DK2.js";
|
|
6
6
|
import "../chunk-Q3TJAJ54.js";
|
|
7
7
|
|
|
8
8
|
// src/route/signed-url.ts
|
|
@@ -203,6 +203,7 @@ function resolveConvaiTtsModel(requested) {
|
|
|
203
203
|
if (requested && CONVAI_SUPPORTED_TTS_MODELS.has(requested)) return requested;
|
|
204
204
|
return CONVAI_TTS_MODEL_FALLBACK;
|
|
205
205
|
}
|
|
206
|
+
var DEFAULT_MUSIC_MODEL = "music_v1";
|
|
206
207
|
var ElevenLabsApiError = class extends Error {
|
|
207
208
|
constructor(message, status, body) {
|
|
208
209
|
super(message);
|
|
@@ -403,6 +404,110 @@ var ElevenLabsClient = class {
|
|
|
403
404
|
const contentType = res.headers.get("content-type") ?? accept;
|
|
404
405
|
return { audio, modelId: used, contentType };
|
|
405
406
|
}
|
|
407
|
+
/**
|
|
408
|
+
* Low-level escape hatch for any ElevenLabs endpoint not covered by a
|
|
409
|
+
* dedicated method (music, sound-generation, speech-to-text, dubbing,
|
|
410
|
+
* voice-design, …). Handles auth, query encoding, JSON body encoding,
|
|
411
|
+
* and JSON-vs-binary response parsing; non-2xx responses throw
|
|
412
|
+
* {@link ElevenLabsApiError} like the typed methods do.
|
|
413
|
+
*
|
|
414
|
+
* @example
|
|
415
|
+
* ```ts
|
|
416
|
+
* const { json } = await el.request({ method: 'GET', path: '/v1/models' })
|
|
417
|
+
* const { audio } = await el.request({
|
|
418
|
+
* method: 'POST',
|
|
419
|
+
* path: '/v1/sound-generation',
|
|
420
|
+
* body: { text: 'door creak' },
|
|
421
|
+
* accept: 'audio/mpeg',
|
|
422
|
+
* })
|
|
423
|
+
* ```
|
|
424
|
+
*/
|
|
425
|
+
async request(opts) {
|
|
426
|
+
const accept = opts.accept ?? "application/json";
|
|
427
|
+
let url = `${this.baseUrl}${opts.path.startsWith("/") ? "" : "/"}${opts.path}`;
|
|
428
|
+
if (opts.query) {
|
|
429
|
+
const params = new URLSearchParams();
|
|
430
|
+
for (const [k, v] of Object.entries(opts.query)) {
|
|
431
|
+
if (v !== null && v !== void 0) params.set(k, String(v));
|
|
432
|
+
}
|
|
433
|
+
const qs = params.toString();
|
|
434
|
+
if (qs) url += `${url.includes("?") ? "&" : "?"}${qs}`;
|
|
435
|
+
}
|
|
436
|
+
const headers = this.headers({ accept, ...opts.headers ?? {} });
|
|
437
|
+
const init = { method: opts.method, headers };
|
|
438
|
+
if (opts.body !== void 0 && opts.body !== null) {
|
|
439
|
+
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);
|
|
440
|
+
if (isPlainPayload) {
|
|
441
|
+
headers["content-type"] = "application/json";
|
|
442
|
+
init.body = JSON.stringify(opts.body);
|
|
443
|
+
} else {
|
|
444
|
+
init.body = opts.body;
|
|
445
|
+
}
|
|
446
|
+
}
|
|
447
|
+
const res = await this.fetchImpl(url, init);
|
|
448
|
+
if (!res.ok) {
|
|
449
|
+
const text = await res.text();
|
|
450
|
+
throw new ElevenLabsApiError(
|
|
451
|
+
`request ${opts.method} ${opts.path} failed: ${res.status}`,
|
|
452
|
+
res.status,
|
|
453
|
+
text
|
|
454
|
+
);
|
|
455
|
+
}
|
|
456
|
+
const contentType = res.headers.get("content-type") ?? accept;
|
|
457
|
+
if (contentType.includes("application/json")) {
|
|
458
|
+
return { status: res.status, contentType, json: await res.json() };
|
|
459
|
+
}
|
|
460
|
+
return { status: res.status, contentType, audio: await res.arrayBuffer() };
|
|
461
|
+
}
|
|
462
|
+
/**
|
|
463
|
+
* Generate music via `POST /v1/music`. Provide exactly one of `prompt`
|
|
464
|
+
* or `compositionPlan` (ElevenLabs returns 422 otherwise). Returns the
|
|
465
|
+
* raw audio bytes plus the response content type.
|
|
466
|
+
*
|
|
467
|
+
* @example
|
|
468
|
+
* ```ts
|
|
469
|
+
* const { audio } = await el.composeMusic({
|
|
470
|
+
* prompt: 'Upbeat synthwave with driving bass',
|
|
471
|
+
* musicLengthMs: 30_000,
|
|
472
|
+
* })
|
|
473
|
+
* ```
|
|
474
|
+
*/
|
|
475
|
+
async composeMusic(params) {
|
|
476
|
+
const hasPrompt = typeof params.prompt === "string" && params.prompt.length > 0;
|
|
477
|
+
const hasPlan = params.compositionPlan != null;
|
|
478
|
+
if (hasPrompt === hasPlan) {
|
|
479
|
+
throw new Error(
|
|
480
|
+
"composeMusic: provide exactly one of `prompt` or `compositionPlan`"
|
|
481
|
+
);
|
|
482
|
+
}
|
|
483
|
+
const body = {
|
|
484
|
+
model_id: params.modelId ?? DEFAULT_MUSIC_MODEL
|
|
485
|
+
};
|
|
486
|
+
if (hasPrompt) {
|
|
487
|
+
body.prompt = params.prompt;
|
|
488
|
+
if (params.musicLengthMs !== void 0) body.music_length_ms = params.musicLengthMs;
|
|
489
|
+
if (params.forceInstrumental !== void 0) {
|
|
490
|
+
body.force_instrumental = params.forceInstrumental;
|
|
491
|
+
}
|
|
492
|
+
} else {
|
|
493
|
+
body.composition_plan = params.compositionPlan;
|
|
494
|
+
}
|
|
495
|
+
const { audio, contentType } = await this.request({
|
|
496
|
+
method: "POST",
|
|
497
|
+
path: "/v1/music",
|
|
498
|
+
body,
|
|
499
|
+
accept: "audio/mpeg",
|
|
500
|
+
...params.outputFormat ? { query: { output_format: params.outputFormat } } : {}
|
|
501
|
+
});
|
|
502
|
+
if (!audio) {
|
|
503
|
+
throw new ElevenLabsApiError(
|
|
504
|
+
"composeMusic: expected audio bytes but got a JSON response",
|
|
505
|
+
500,
|
|
506
|
+
""
|
|
507
|
+
);
|
|
508
|
+
}
|
|
509
|
+
return { audio, contentType };
|
|
510
|
+
}
|
|
406
511
|
/** Probe whether a given voice id still exists on the caller's account. */
|
|
407
512
|
async voiceExists(voiceId) {
|
|
408
513
|
const res = await this.fetchImpl(
|
|
@@ -584,6 +689,28 @@ async function readJson(req) {
|
|
|
584
689
|
}
|
|
585
690
|
var NOOP_LOGGER = () => {
|
|
586
691
|
};
|
|
692
|
+
function parseMusicBody(raw) {
|
|
693
|
+
if (raw === null || typeof raw !== "object") return { error: "Invalid JSON body" };
|
|
694
|
+
const b = raw;
|
|
695
|
+
const hasPrompt = typeof b.prompt === "string" && b.prompt.trim().length > 0;
|
|
696
|
+
const hasPlan = b.compositionPlan != null && typeof b.compositionPlan === "object";
|
|
697
|
+
if (hasPrompt === hasPlan) {
|
|
698
|
+
return { error: "Provide exactly one of `prompt` or `compositionPlan`" };
|
|
699
|
+
}
|
|
700
|
+
const params = {};
|
|
701
|
+
if (hasPrompt) {
|
|
702
|
+
params.prompt = b.prompt.trim();
|
|
703
|
+
if (typeof b.musicLengthMs === "number") params.musicLengthMs = b.musicLengthMs;
|
|
704
|
+
if (typeof b.forceInstrumental === "boolean") params.forceInstrumental = b.forceInstrumental;
|
|
705
|
+
} else {
|
|
706
|
+
params.compositionPlan = b.compositionPlan;
|
|
707
|
+
}
|
|
708
|
+
if (typeof b.modelId === "string" && b.modelId.length > 0) params.modelId = b.modelId;
|
|
709
|
+
if (typeof b.outputFormat === "string" && b.outputFormat.length > 0) {
|
|
710
|
+
params.outputFormat = b.outputFormat;
|
|
711
|
+
}
|
|
712
|
+
return { params };
|
|
713
|
+
}
|
|
587
714
|
function resolveProxyOptions(config) {
|
|
588
715
|
if (config.proxy) {
|
|
589
716
|
if (!config.proxy.runtimeToken || !config.proxy.projectId || !config.proxy.apiUrl) {
|
|
@@ -669,6 +796,34 @@ function createProxyHandlers(proxy, log) {
|
|
|
669
796
|
}
|
|
670
797
|
});
|
|
671
798
|
}
|
|
799
|
+
async function doMusic(req) {
|
|
800
|
+
if (req.method !== "POST") return json({ error: "Method Not Allowed" }, 405);
|
|
801
|
+
const raw = await readJson(req);
|
|
802
|
+
const parsed = parseMusicBody(raw);
|
|
803
|
+
if ("error" in parsed) return json({ error: parsed.error }, 400);
|
|
804
|
+
try {
|
|
805
|
+
const upstream = await fetchImpl(proxyUrl("/api/voice/music", {}), {
|
|
806
|
+
method: "POST",
|
|
807
|
+
headers: {
|
|
808
|
+
"x-runtime-token": proxy.runtimeToken,
|
|
809
|
+
"content-type": "application/json",
|
|
810
|
+
accept: "audio/mpeg"
|
|
811
|
+
},
|
|
812
|
+
body: JSON.stringify(parsed.params),
|
|
813
|
+
credentials: "omit"
|
|
814
|
+
});
|
|
815
|
+
const buf = await upstream.arrayBuffer();
|
|
816
|
+
const headers = {};
|
|
817
|
+
const ct = upstream.headers.get("content-type");
|
|
818
|
+
if (ct) headers["content-type"] = ct;
|
|
819
|
+
return new Response(buf, { status: upstream.status, headers });
|
|
820
|
+
} catch (err) {
|
|
821
|
+
log("error", "proxy music failed", {
|
|
822
|
+
error: err instanceof Error ? err.message : String(err)
|
|
823
|
+
});
|
|
824
|
+
return json({ error: "music proxy failed" }, 502);
|
|
825
|
+
}
|
|
826
|
+
}
|
|
672
827
|
function notImplemented(label) {
|
|
673
828
|
return async () => json(
|
|
674
829
|
{
|
|
@@ -681,6 +836,7 @@ function createProxyHandlers(proxy, log) {
|
|
|
681
836
|
return {
|
|
682
837
|
signedUrl: doSignedUrl,
|
|
683
838
|
tts: notImplemented("tts"),
|
|
839
|
+
music: doMusic,
|
|
684
840
|
agent: {
|
|
685
841
|
create: notImplemented("agent.create"),
|
|
686
842
|
patch: notImplemented("agent.patch"),
|
|
@@ -821,6 +977,25 @@ function createVoiceHandlers(config) {
|
|
|
821
977
|
return handleElError(e, "TTS failed");
|
|
822
978
|
}
|
|
823
979
|
}
|
|
980
|
+
async function doMusic(req) {
|
|
981
|
+
if (req.method !== "POST") return json({ error: "Method Not Allowed" }, 405);
|
|
982
|
+
const u = await resolveUser(req);
|
|
983
|
+
if (u instanceof Response) return u;
|
|
984
|
+
const raw = await readJson(req);
|
|
985
|
+
const parsed = parseMusicBody(raw);
|
|
986
|
+
if ("error" in parsed) return json({ error: parsed.error }, 400);
|
|
987
|
+
try {
|
|
988
|
+
const { audio, contentType } = await el.composeMusic(parsed.params);
|
|
989
|
+
return new Response(audio, {
|
|
990
|
+
headers: {
|
|
991
|
+
"content-type": contentType,
|
|
992
|
+
"cache-control": "private, max-age=3600"
|
|
993
|
+
}
|
|
994
|
+
});
|
|
995
|
+
} catch (e) {
|
|
996
|
+
return handleElError(e, "Music generation failed");
|
|
997
|
+
}
|
|
998
|
+
}
|
|
824
999
|
async function doAgentCreate(req) {
|
|
825
1000
|
if (req.method !== "POST") return json({ error: "Method Not Allowed" }, 405);
|
|
826
1001
|
const u = await resolveUser(req);
|
|
@@ -951,6 +1126,7 @@ function createVoiceHandlers(config) {
|
|
|
951
1126
|
return {
|
|
952
1127
|
signedUrl: doSignedUrl,
|
|
953
1128
|
tts: doTts,
|
|
1129
|
+
music: doMusic,
|
|
954
1130
|
agent: {
|
|
955
1131
|
create: doAgentCreate,
|
|
956
1132
|
patch: doAgentPatch,
|
|
@@ -1,8 +1,8 @@
|
|
|
1
1
|
import {
|
|
2
2
|
defaultHandlers
|
|
3
|
-
} from "../chunk-
|
|
4
|
-
import "../chunk-
|
|
5
|
-
import "../chunk-
|
|
3
|
+
} from "../chunk-T3T4EBWY.js";
|
|
4
|
+
import "../chunk-O362EJQR.js";
|
|
5
|
+
import "../chunk-B4JC3DK2.js";
|
|
6
6
|
import "../chunk-Q3TJAJ54.js";
|
|
7
7
|
|
|
8
8
|
// src/route/tts-preview.ts
|