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