@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.
@@ -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-ZGYHDC5G.js.map
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-ZGYHDC5G.js";
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-QHDIEE7O.js.map
524
+ //# sourceMappingURL=chunk-O362EJQR.js.map
@@ -1,6 +1,6 @@
1
1
  import {
2
2
  createVoiceHandlers
3
- } from "./chunk-QHDIEE7O.js";
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-D2EVSUZX.js.map
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-BKJtOUbE.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 ConvaiClientTool, h as CreateAgentParams, i as CreateCompanionBody, D as DEFAULT_ALLOWED_TAGS, j as DEFAULT_ELEVENLABS_BASE_URL, k as DEFAULT_VOICE_SETTINGS, l as EXPRESSIVITY_BLOCK_CLOSE, m as EXPRESSIVITY_BLOCK_OPEN, n as EXPRESSIVITY_OPTIONS, o as ElevenLabsApiError, p as ElevenLabsClient, q as ElevenLabsClientConfig, M as MEMORY_CLIENT_TOOLS, P as PatchAgentParams, r as PatchCompanionBody, T as TextToSpeechParams, s as TtsPreviewBody, V as VoiceMemoryStore, t as VoiceSettings, u as VoiceUser, v as buildPreviewLine, w as composeExpressivityBlock, x as normalizeAudioTags, y as normalizeExpressivity, z as normalizeVoiceSettings, B as readAudioTags, F as readExpressivity, G as readVoiceSettings, H as resolveConvaiTtsModel, I as stripAudioTags, J as stripExpressivityBlock } from './types-BKJtOUbE.cjs';
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-BKJtOUbE.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 ConvaiClientTool, h as CreateAgentParams, i as CreateCompanionBody, D as DEFAULT_ALLOWED_TAGS, j as DEFAULT_ELEVENLABS_BASE_URL, k as DEFAULT_VOICE_SETTINGS, l as EXPRESSIVITY_BLOCK_CLOSE, m as EXPRESSIVITY_BLOCK_OPEN, n as EXPRESSIVITY_OPTIONS, o as ElevenLabsApiError, p as ElevenLabsClient, q as ElevenLabsClientConfig, M as MEMORY_CLIENT_TOOLS, P as PatchAgentParams, r as PatchCompanionBody, T as TextToSpeechParams, s as TtsPreviewBody, V as VoiceMemoryStore, t as VoiceSettings, u as VoiceUser, v as buildPreviewLine, w as composeExpressivityBlock, x as normalizeAudioTags, y as normalizeExpressivity, z as normalizeVoiceSettings, B as readAudioTags, F as readExpressivity, G as readVoiceSettings, H as resolveConvaiTtsModel, I as stripAudioTags, J as stripExpressivityBlock } from './types-BKJtOUbE.js';
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-ZGYHDC5G.js";
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,
@@ -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,