@shogo-ai/voice 1.9.13 → 2.4.1

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.
Files changed (68) hide show
  1. package/README.md +89 -0
  2. package/dist/{chunk-S3S2RNO6.js → chunk-BI6SL35K.js} +1 -184
  3. package/dist/chunk-CLF25CZC.js +987 -0
  4. package/dist/chunk-KMSL5PWK.js +540 -0
  5. package/dist/chunk-ME3SIZZU.js +581 -0
  6. package/dist/chunk-TQOBJMEK.js +17 -0
  7. package/dist/{chunk-T3T4EBWY.js → chunk-UH75UVM2.js} +6 -2
  8. package/dist/chunk-UW7VQ3S6.js +148 -0
  9. package/dist/chunk-XE4LLC7Z.js +1067 -0
  10. package/dist/chunk-ZAW6F3YT.js +172 -0
  11. package/dist/index.cjs +735 -25
  12. package/dist/index.d.cts +373 -3
  13. package/dist/index.d.ts +373 -3
  14. package/dist/index.js +59 -3
  15. package/dist/live/client.cjs +173 -0
  16. package/dist/live/client.d.cts +132 -0
  17. package/dist/live/client.d.ts +132 -0
  18. package/dist/live/client.js +9 -0
  19. package/dist/native/index.d.cts +5 -2
  20. package/dist/native/index.d.ts +5 -2
  21. package/dist/native/index.js +15 -11
  22. package/dist/{organicParticlesShaders-DIva0vBO.d.ts → organicParticlesShaders-seZq7jYX.d.cts} +1 -421
  23. package/dist/{organicParticlesShaders-DIva0vBO.d.cts → organicParticlesShaders-seZq7jYX.d.ts} +1 -421
  24. package/dist/particlesConfig-0YbdPOdY.d.cts +223 -0
  25. package/dist/particlesConfig-0YbdPOdY.d.ts +223 -0
  26. package/dist/react/conversation.cjs +789 -0
  27. package/dist/react/conversation.d.cts +134 -0
  28. package/dist/react/conversation.d.ts +134 -0
  29. package/dist/react/conversation.js +13 -0
  30. package/dist/react/index.cjs +374 -36
  31. package/dist/react/index.d.cts +10 -141
  32. package/dist/react/index.d.ts +10 -141
  33. package/dist/react/index.js +23 -770
  34. package/dist/react/visuals.cjs +1048 -0
  35. package/dist/react/visuals.d.cts +70 -0
  36. package/dist/react/visuals.d.ts +70 -0
  37. package/dist/react/visuals.js +10 -0
  38. package/dist/route/agent.cjs +1038 -85
  39. package/dist/route/agent.js +3 -3
  40. package/dist/route/audio-tags.cjs +1038 -85
  41. package/dist/route/audio-tags.js +3 -3
  42. package/dist/route/index.cjs +1042 -85
  43. package/dist/route/index.d.cts +19 -3
  44. package/dist/route/index.d.ts +19 -3
  45. package/dist/route/index.js +3 -3
  46. package/dist/route/music.cjs +1038 -85
  47. package/dist/route/music.js +3 -3
  48. package/dist/route/op.cjs +2118 -0
  49. package/dist/route/op.d.cts +43 -0
  50. package/dist/route/op.d.ts +43 -0
  51. package/dist/route/op.js +27 -0
  52. package/dist/route/signed-url.cjs +1038 -85
  53. package/dist/route/signed-url.js +3 -3
  54. package/dist/route/tts-preview.cjs +1038 -85
  55. package/dist/route/tts-preview.d.cts +6 -4
  56. package/dist/route/tts-preview.d.ts +6 -4
  57. package/dist/route/tts-preview.js +3 -3
  58. package/dist/server.cjs +1038 -85
  59. package/dist/server.d.cts +40 -8
  60. package/dist/server.d.ts +40 -8
  61. package/dist/server.js +2 -2
  62. package/dist/types-Bfhu-aQH.d.cts +199 -0
  63. package/dist/types-Bfhu-aQH.d.ts +199 -0
  64. package/dist/{types-W9y6eMj3.d.ts → types-CadjTXNH.d.cts} +193 -12
  65. package/dist/{types-W9y6eMj3.d.cts → types-CadjTXNH.d.ts} +193 -12
  66. package/package.json +21 -1
  67. package/dist/chunk-B4JC3DK2.js +0 -529
  68. package/dist/chunk-O362EJQR.js +0 -524
package/dist/index.cjs CHANGED
@@ -28,6 +28,10 @@ __export(index_exports, {
28
28
  DEFAULT_ELEVENLABS_BASE_URL: () => DEFAULT_ELEVENLABS_BASE_URL,
29
29
  DEFAULT_MEMORY_BLOCK: () => DEFAULT_MEMORY_BLOCK,
30
30
  DEFAULT_MUSIC_MODEL: () => DEFAULT_MUSIC_MODEL,
31
+ DEFAULT_SOUND_GENERATION_MODEL: () => DEFAULT_SOUND_GENERATION_MODEL,
32
+ DEFAULT_SPEECH_TO_TEXT_MODEL: () => DEFAULT_SPEECH_TO_TEXT_MODEL,
33
+ DEFAULT_STS_MODEL: () => DEFAULT_STS_MODEL,
34
+ DEFAULT_TTS_MODEL: () => DEFAULT_TTS_MODEL,
31
35
  DEFAULT_TWILIO_BASE_URL: () => DEFAULT_TWILIO_BASE_URL,
32
36
  DEFAULT_VOICE_SETTINGS: () => DEFAULT_VOICE_SETTINGS,
33
37
  DirectTelephonyClient: () => DirectTelephonyClient,
@@ -38,28 +42,51 @@ __export(index_exports, {
38
42
  ElevenLabsClient: () => ElevenLabsClient,
39
43
  HostedRuntimeTokenClient: () => HostedRuntimeTokenClient,
40
44
  HostedTelephonyClient: () => HostedTelephonyClient,
45
+ LiveSession: () => LiveSession,
41
46
  MEMORY_CLIENT_TOOLS: () => MEMORY_CLIENT_TOOLS,
42
47
  MockTelephonyClient: () => MockTelephonyClient,
48
+ ShogoLiveClient: () => ShogoLiveClient,
49
+ TTS_FALLBACK_MODEL: () => TTS_FALLBACK_MODEL,
43
50
  TelephonyApiError: () => TelephonyApiError,
44
51
  TelephonyConfigError: () => TelephonyConfigError,
45
52
  TwilioApiError: () => TwilioApiError,
46
53
  TwilioClient: () => TwilioClient,
54
+ VOICE_OPS: () => VOICE_OPS,
55
+ VOICE_OP_IDS: () => VOICE_OP_IDS,
56
+ VOICE_SOUND_GENERATION_MODEL_IDS: () => VOICE_SOUND_GENERATION_MODEL_IDS,
57
+ VOICE_SPEECH_TO_TEXT_MODEL_IDS: () => VOICE_SPEECH_TO_TEXT_MODEL_IDS,
58
+ VOICE_STS_MODEL_IDS: () => VOICE_STS_MODEL_IDS,
59
+ VOICE_TTS_MODELS: () => VOICE_TTS_MODELS,
60
+ VOICE_TTS_MODEL_IDS: () => VOICE_TTS_MODEL_IDS,
47
61
  buildPreviewLine: () => buildPreviewLine,
48
62
  composeAgentPrompt: () => composeAgentPrompt,
49
63
  composeExpressivityBlock: () => composeExpressivityBlock,
50
64
  createTelephonyClient: () => createTelephonyClient,
51
65
  extractBasePrompt: () => extractBasePrompt,
66
+ getVoiceOp: () => getVoiceOp,
67
+ getVoiceTtsModel: () => getVoiceTtsModel,
68
+ isModelRelatedTtsFailure: () => isModelRelatedTtsFailure,
52
69
  isVoiceMockEnv: () => isVoiceMockEnv,
53
70
  normalizeAudioTags: () => normalizeAudioTags,
54
71
  normalizeExpressivity: () => normalizeExpressivity,
55
72
  normalizeVoiceSettings: () => normalizeVoiceSettings,
73
+ parseVoiceOpForm: () => parseVoiceOpForm,
74
+ parseVoiceOpJsonBody: () => parseVoiceOpJsonBody,
56
75
  readAudioTags: () => readAudioTags,
57
76
  readExpressivity: () => readExpressivity,
58
77
  readVoiceSettings: () => readVoiceSettings,
59
78
  resolveConvaiTtsModel: () => resolveConvaiTtsModel,
79
+ resolveConvaiTtsModelDetailed: () => resolveConvaiTtsModelDetailed,
60
80
  stripAudioTags: () => stripAudioTags,
61
81
  stripExpressivityBlock: () => stripExpressivityBlock,
62
- stripMemoryBlock: () => stripMemoryBlock
82
+ stripMemoryBlock: () => stripMemoryBlock,
83
+ voiceOpCredentialError: () => voiceOpCredentialError,
84
+ voiceOpForwardPath: () => voiceOpForwardPath,
85
+ voiceOpIsMultipart: () => voiceOpIsMultipart,
86
+ voiceOpPathParams: () => voiceOpPathParams,
87
+ voiceOpReturnsAudio: () => voiceOpReturnsAudio,
88
+ voiceOpUpstreamBody: () => voiceOpUpstreamBody,
89
+ voiceOpUpstreamPath: () => voiceOpUpstreamPath
63
90
  });
64
91
  module.exports = __toCommonJS(index_exports);
65
92
 
@@ -237,19 +264,74 @@ function composeAgentPrompt(basePrompt, options = {}) {
237
264
 
238
265
  // src/elevenlabs.ts
239
266
  var DEFAULT_ELEVENLABS_BASE_URL = "https://api.elevenlabs.io";
240
- var CONVAI_SUPPORTED_TTS_MODELS = /* @__PURE__ */ new Set([
241
- "eleven_turbo_v2",
242
- "eleven_turbo_v2_5",
243
- "eleven_flash_v2",
244
- "eleven_flash_v2_5",
245
- "eleven_multilingual_v2"
246
- ]);
247
- var CONVAI_TTS_MODEL_FALLBACK = "eleven_turbo_v2_5";
267
+ var VOICE_TTS_MODELS = [
268
+ { id: "eleven_v4", label: "Eleven v4", realtime: false, maxChars: 1e4 },
269
+ { id: "eleven_v4_turbo", label: "Eleven v4 Turbo", realtime: true, maxChars: 1e4 },
270
+ { id: "eleven_v3", label: "Eleven v3", realtime: false, maxChars: 5e3 },
271
+ {
272
+ id: "eleven_v3_conversational",
273
+ label: "Eleven v3 Conversational",
274
+ realtime: true,
275
+ maxChars: 5e3
276
+ },
277
+ { id: "eleven_multilingual_v2", label: "Eleven Multilingual v2", realtime: false, maxChars: 1e4 },
278
+ { id: "eleven_flash_v2_5", label: "Eleven Flash v2.5", realtime: true, maxChars: 4e4 },
279
+ { id: "eleven_flash_v2", label: "Eleven Flash v2", realtime: true, maxChars: 3e4 },
280
+ { id: "eleven_turbo_v2_5", label: "Eleven Turbo v2.5", realtime: true, maxChars: 4e4 },
281
+ { id: "eleven_turbo_v2", label: "Eleven Turbo v2", realtime: true, maxChars: 3e4, deprecated: true }
282
+ ];
283
+ var VOICE_TTS_MODEL_IDS = VOICE_TTS_MODELS.map((m) => m.id);
284
+ var DEFAULT_TTS_MODEL = "eleven_v4";
285
+ var CONVAI_SUPPORTED_TTS_MODELS = new Set(
286
+ VOICE_TTS_MODELS.filter((m) => m.realtime).map((m) => m.id)
287
+ );
288
+ var CONVAI_TTS_MODEL_FALLBACK = "eleven_v4_turbo";
289
+ var TTS_FALLBACK_MODEL = "eleven_flash_v2_5";
290
+ function getVoiceTtsModel(modelId) {
291
+ if (!modelId) return void 0;
292
+ return VOICE_TTS_MODELS.find((m) => m.id === modelId);
293
+ }
294
+ var loggedDowngrades = /* @__PURE__ */ new Set();
295
+ function resolveConvaiTtsModelDetailed(requested) {
296
+ if (requested && CONVAI_SUPPORTED_TTS_MODELS.has(requested)) {
297
+ return { modelId: requested, requested, downgraded: false };
298
+ }
299
+ const known = getVoiceTtsModel(requested);
300
+ const reason = !requested ? void 0 : known ? `${requested} is not supported by the convai realtime runtime` : `unknown TTS model ${requested}`;
301
+ if (reason && !loggedDowngrades.has(requested)) {
302
+ loggedDowngrades.add(requested);
303
+ try {
304
+ console.warn(
305
+ `[voice] convai TTS model ${requested} is unavailable (${reason}); using ${CONVAI_TTS_MODEL_FALLBACK}`
306
+ );
307
+ } catch {
308
+ }
309
+ }
310
+ return {
311
+ modelId: CONVAI_TTS_MODEL_FALLBACK,
312
+ requested: requested ?? null,
313
+ downgraded: Boolean(requested),
314
+ ...reason ? { reason } : {}
315
+ };
316
+ }
248
317
  function resolveConvaiTtsModel(requested) {
249
- if (requested && CONVAI_SUPPORTED_TTS_MODELS.has(requested)) return requested;
250
- return CONVAI_TTS_MODEL_FALLBACK;
318
+ return resolveConvaiTtsModelDetailed(requested).modelId;
319
+ }
320
+ function isModelRelatedTtsFailure(status, body) {
321
+ if (status === 402) return true;
322
+ if (status !== 400 && status !== 403 && status !== 404 && status !== 422) return false;
323
+ return /model|plan|permission/i.test(body);
251
324
  }
252
325
  var DEFAULT_MUSIC_MODEL = "music_v1";
326
+ var VOICE_STS_MODEL_IDS = [
327
+ "eleven_multilingual_sts_v2",
328
+ "eleven_english_sts_v2"
329
+ ];
330
+ var DEFAULT_STS_MODEL = "eleven_multilingual_sts_v2";
331
+ var VOICE_SPEECH_TO_TEXT_MODEL_IDS = ["scribe_v1"];
332
+ var DEFAULT_SPEECH_TO_TEXT_MODEL = "scribe_v1";
333
+ var VOICE_SOUND_GENERATION_MODEL_IDS = ["eleven_text_to_sound_v2"];
334
+ var DEFAULT_SOUND_GENERATION_MODEL = "eleven_text_to_sound_v2";
253
335
  var ElevenLabsApiError = class extends Error {
254
336
  constructor(message, status, body) {
255
337
  super(message);
@@ -414,12 +496,18 @@ var ElevenLabsClient = class {
414
496
  return data.signed_url;
415
497
  }
416
498
  /**
417
- * One-shot TTS. Returns the raw audio bytes plus the model id that was
418
- * actually used (some models aren't available for every account, so we
419
- * fall back to `eleven_turbo_v2_5` on the first failure).
499
+ * One-shot TTS. Returns the raw audio bytes plus the model that actually
500
+ * produced them.
501
+ *
502
+ * The requested model is never swapped silently: we only retry on
503
+ * {@link TTS_FALLBACK_MODEL} when the failure is plausibly about
504
+ * the model (400/403/404/422 whose body mentions model/plan/permission,
505
+ * or 402 for an out-of-plan model). Auth (401), rate limit (429), 5xx and
506
+ * network errors are surfaced as-is. When a fallback does happen the
507
+ * result reports `requestedModel`, `fellBack` and `fallbackReason`.
420
508
  */
421
509
  async textToSpeech(params) {
422
- const modelId = params.modelId && params.modelId.length > 0 ? params.modelId : "eleven_v3";
510
+ const requestedModel = params.modelId && params.modelId.length > 0 ? params.modelId : DEFAULT_TTS_MODEL;
423
511
  const accept = params.accept ?? "audio/mpeg";
424
512
  const attempt = async (m) => {
425
513
  const res2 = await this.fetchImpl(
@@ -436,19 +524,39 @@ var ElevenLabsClient = class {
436
524
  );
437
525
  return res2;
438
526
  };
439
- let res = await attempt(modelId);
440
- let used = modelId;
441
- if (!res.ok && modelId !== "eleven_turbo_v2_5") {
442
- res = await attempt("eleven_turbo_v2_5");
443
- used = "eleven_turbo_v2_5";
444
- }
527
+ let res = await attempt(requestedModel);
528
+ let used = requestedModel;
529
+ let fellBack = false;
530
+ let fallbackReason;
445
531
  if (!res.ok) {
446
- const text = await res.text();
447
- throw new ElevenLabsApiError(`textToSpeech failed: ${res.status}`, res.status, text);
532
+ const body = await res.text();
533
+ if (requestedModel !== TTS_FALLBACK_MODEL && isModelRelatedTtsFailure(res.status, body)) {
534
+ fallbackReason = `elevenlabs ${res.status} for ${requestedModel}: ${body.slice(0, 200)}`;
535
+ res = await attempt(TTS_FALLBACK_MODEL);
536
+ used = TTS_FALLBACK_MODEL;
537
+ fellBack = true;
538
+ if (!res.ok) {
539
+ const retryBody = await res.text();
540
+ throw new ElevenLabsApiError(
541
+ `textToSpeech failed: ${res.status}`,
542
+ res.status,
543
+ retryBody
544
+ );
545
+ }
546
+ } else {
547
+ throw new ElevenLabsApiError(`textToSpeech failed: ${res.status}`, res.status, body);
548
+ }
448
549
  }
449
550
  const audio = await res.arrayBuffer();
450
551
  const contentType = res.headers.get("content-type") ?? accept;
451
- return { audio, modelId: used, contentType };
552
+ return {
553
+ audio,
554
+ modelId: used,
555
+ requestedModel,
556
+ fellBack,
557
+ ...fallbackReason ? { fallbackReason } : {},
558
+ contentType
559
+ };
452
560
  }
453
561
  /**
454
562
  * Low-level escape hatch for any ElevenLabs endpoint not covered by a
@@ -554,6 +662,119 @@ var ElevenLabsClient = class {
554
662
  }
555
663
  return { audio, contentType };
556
664
  }
665
+ /**
666
+ * Convert speech to speech — the "voice changer": keep the performance,
667
+ * swap the voice. `POST /v1/speech-to-speech/{voiceId}` with a multipart
668
+ * audio part. Returns the converted audio bytes.
669
+ *
670
+ * @example
671
+ * ```ts
672
+ * const { audio } = await el.speechToSpeech({
673
+ * voiceId: '21m00Tcm4TlvDq8ikWAM',
674
+ * audio: await fetch(myRecording).then((r) => r.blob()),
675
+ * modelId: 'eleven_multilingual_sts_v2',
676
+ * })
677
+ * ```
678
+ */
679
+ async speechToSpeech(params) {
680
+ if (!params.voiceId) throw new Error("speechToSpeech: voiceId is required");
681
+ if (!params.audio) throw new Error("speechToSpeech: audio is required");
682
+ const form = new FormData();
683
+ form.set("audio", params.audio, params.fileName ?? "audio");
684
+ form.set("model_id", params.modelId ?? DEFAULT_STS_MODEL);
685
+ if (params.voiceSettings) {
686
+ form.set("voice_settings", JSON.stringify(params.voiceSettings));
687
+ }
688
+ if (params.outputFormat) form.set("output_format", params.outputFormat);
689
+ if (params.fileFormat) form.set("file_format", params.fileFormat);
690
+ if (params.seed !== void 0) form.set("seed", String(params.seed));
691
+ if (params.removeBackgroundNoise !== void 0) {
692
+ form.set("remove_background_noise", String(params.removeBackgroundNoise));
693
+ }
694
+ const { audio, contentType } = await this.request({
695
+ method: "POST",
696
+ path: `/v1/speech-to-speech/${encodeURIComponent(params.voiceId)}`,
697
+ body: form,
698
+ accept: "audio/mpeg"
699
+ });
700
+ if (!audio) {
701
+ throw new ElevenLabsApiError(
702
+ "speechToSpeech: expected audio bytes but got a JSON response",
703
+ 500,
704
+ ""
705
+ );
706
+ }
707
+ return { audio, contentType };
708
+ }
709
+ /**
710
+ * Generate a sound effect from a text prompt. `POST /v1/sound-generation`.
711
+ *
712
+ * @example
713
+ * ```ts
714
+ * const { audio } = await el.soundEffects({ text: 'heavy wooden door creaking' })
715
+ * ```
716
+ */
717
+ async soundEffects(params) {
718
+ if (!params.text || !params.text.trim()) {
719
+ throw new Error("soundEffects: text is required");
720
+ }
721
+ const body = {
722
+ text: params.text,
723
+ model_id: params.modelId ?? DEFAULT_SOUND_GENERATION_MODEL
724
+ };
725
+ if (params.durationSeconds !== void 0) body.duration_seconds = params.durationSeconds;
726
+ if (params.promptInfluence !== void 0) body.prompt_influence = params.promptInfluence;
727
+ if (params.loop !== void 0) body.loop = params.loop;
728
+ const { audio, contentType } = await this.request({
729
+ method: "POST",
730
+ path: "/v1/sound-generation",
731
+ body,
732
+ accept: "audio/mpeg",
733
+ ...params.outputFormat ? { query: { output_format: params.outputFormat } } : {}
734
+ });
735
+ if (!audio) {
736
+ throw new ElevenLabsApiError(
737
+ "soundEffects: expected audio bytes but got a JSON response",
738
+ 500,
739
+ ""
740
+ );
741
+ }
742
+ return { audio, contentType };
743
+ }
744
+ /**
745
+ * Transcribe audio with Scribe. `POST /v1/speech-to-text` (multipart).
746
+ *
747
+ * @example
748
+ * ```ts
749
+ * const { text } = await el.transcribe({ file: recording })
750
+ * ```
751
+ */
752
+ async transcribe(params) {
753
+ if (!params.file) throw new Error("transcribe: file is required");
754
+ const form = new FormData();
755
+ form.set("file", params.file, params.fileName ?? "audio");
756
+ form.set("model_id", params.modelId ?? DEFAULT_SPEECH_TO_TEXT_MODEL);
757
+ if (params.languageCode) form.set("language_code", params.languageCode);
758
+ if (params.diarize !== void 0) form.set("diarize", String(params.diarize));
759
+ if (params.tagAudioEvents !== void 0) {
760
+ form.set("tag_audio_events", String(params.tagAudioEvents));
761
+ }
762
+ if (params.numSpeakers !== void 0) form.set("num_speakers", String(params.numSpeakers));
763
+ const { json } = await this.request({
764
+ method: "POST",
765
+ path: "/v1/speech-to-text",
766
+ body: form,
767
+ accept: "application/json"
768
+ });
769
+ if (!json || typeof json !== "object") {
770
+ throw new ElevenLabsApiError(
771
+ "transcribe: expected a JSON transcription but got audio",
772
+ 500,
773
+ ""
774
+ );
775
+ }
776
+ return json;
777
+ }
557
778
  /** Probe whether a given voice id still exists on the caller's account. */
558
779
  async voiceExists(voiceId) {
559
780
  const res = await this.fetchImpl(
@@ -717,6 +938,325 @@ var MEMORY_CLIENT_TOOLS = [
717
938
  }
718
939
  ];
719
940
 
941
+ // src/ops.ts
942
+ var MAX_AUDIO_BYTES = 25 * 1024 * 1024;
943
+ var MAX_JSON_BYTES = 8 * 1024;
944
+ var MAX_TEXT_BYTES = 256 * 1024;
945
+ var VOICE_OPS = [
946
+ {
947
+ id: "text-to-speech",
948
+ method: "POST",
949
+ hostedPath: "/voice/op/text-to-speech",
950
+ legacyHostedPath: "/voice/tts",
951
+ upstreamPath: "/v1/text-to-speech/{voiceId}",
952
+ pathParams: ["voiceId"],
953
+ transport: "json-binary",
954
+ call: "textToSpeech",
955
+ permission: "project:read",
956
+ maxBodyBytes: MAX_TEXT_BYTES,
957
+ cost: { unit: "character", per: 1 },
958
+ models: VOICE_TTS_MODEL_IDS,
959
+ defaultModel: CONVAI_TTS_MODEL_FALLBACK,
960
+ defaultVoiceId: true,
961
+ forward: {
962
+ text: "text",
963
+ voiceId: "voice_id",
964
+ modelId: "model_id",
965
+ voiceSettings: "voice_settings"
966
+ },
967
+ required: ["text"],
968
+ responseHeaders: ["x-tts-model-used", "x-tts-model-requested", "warning"],
969
+ accept: "audio/mpeg",
970
+ errorLabel: "TTS failed"
971
+ },
972
+ {
973
+ id: "text-to-speech-with-timestamps",
974
+ method: "POST",
975
+ hostedPath: "/voice/op/text-to-speech-with-timestamps",
976
+ upstreamPath: "/v1/text-to-speech/{voiceId}/with-timestamps",
977
+ pathParams: ["voiceId"],
978
+ transport: "json-json",
979
+ permission: "project:read",
980
+ maxBodyBytes: MAX_TEXT_BYTES,
981
+ cost: { unit: "character", per: 1 },
982
+ models: VOICE_TTS_MODEL_IDS,
983
+ defaultModel: CONVAI_TTS_MODEL_FALLBACK,
984
+ defaultVoiceId: true,
985
+ forward: {
986
+ text: "text",
987
+ voiceId: "voice_id",
988
+ modelId: "model_id",
989
+ voiceSettings: "voice_settings",
990
+ outputFormat: "output_format"
991
+ },
992
+ required: ["text"],
993
+ accept: "application/json"
994
+ },
995
+ {
996
+ id: "speech-to-speech",
997
+ method: "POST",
998
+ hostedPath: "/voice/op/speech-to-speech",
999
+ upstreamPath: "/v1/speech-to-speech/{voiceId}",
1000
+ pathParams: ["voiceId"],
1001
+ transport: "multipart-binary",
1002
+ permission: "project:read",
1003
+ maxBodyBytes: MAX_AUDIO_BYTES,
1004
+ cost: { unit: "second", per: 1 },
1005
+ models: VOICE_STS_MODEL_IDS,
1006
+ defaultModel: DEFAULT_STS_MODEL,
1007
+ defaultVoiceId: true,
1008
+ forward: {
1009
+ audio: "audio",
1010
+ voiceId: "voice_id",
1011
+ modelId: "model_id",
1012
+ voiceSettings: "voice_settings",
1013
+ outputFormat: "output_format",
1014
+ fileFormat: "file_format",
1015
+ seed: "seed",
1016
+ removeBackgroundNoise: "remove_background_noise"
1017
+ },
1018
+ required: ["audio"],
1019
+ accept: "audio/mpeg"
1020
+ },
1021
+ {
1022
+ id: "speech-to-text",
1023
+ method: "POST",
1024
+ hostedPath: "/voice/op/speech-to-text",
1025
+ upstreamPath: "/v1/speech-to-text",
1026
+ transport: "multipart-json",
1027
+ permission: "project:read",
1028
+ maxBodyBytes: MAX_AUDIO_BYTES,
1029
+ cost: { unit: "second", per: 1 },
1030
+ models: VOICE_SPEECH_TO_TEXT_MODEL_IDS,
1031
+ defaultModel: DEFAULT_SPEECH_TO_TEXT_MODEL,
1032
+ forward: {
1033
+ file: "file",
1034
+ modelId: "model_id",
1035
+ languageCode: "language_code",
1036
+ diarize: "diarize",
1037
+ tagAudioEvents: "tag_audio_events",
1038
+ numSpeakers: "num_speakers"
1039
+ },
1040
+ required: ["file"],
1041
+ accept: "application/json"
1042
+ },
1043
+ {
1044
+ id: "sound-generation",
1045
+ method: "POST",
1046
+ hostedPath: "/voice/op/sound-generation",
1047
+ upstreamPath: "/v1/sound-generation",
1048
+ transport: "json-binary",
1049
+ permission: "project:read",
1050
+ maxBodyBytes: MAX_JSON_BYTES,
1051
+ cost: { unit: "second", per: 1 },
1052
+ models: VOICE_SOUND_GENERATION_MODEL_IDS,
1053
+ defaultModel: DEFAULT_SOUND_GENERATION_MODEL,
1054
+ forward: {
1055
+ text: "text",
1056
+ durationSeconds: "duration_seconds",
1057
+ promptInfluence: "prompt_influence",
1058
+ loop: "loop",
1059
+ modelId: "model_id"
1060
+ },
1061
+ // `output_format` is a query param on this endpoint, not a body field — the
1062
+ // typed `soundEffects()` client sends it there, and a body field is
1063
+ // silently ignored upstream.
1064
+ queryForward: ["output_format"],
1065
+ required: ["text"],
1066
+ accept: "audio/mpeg"
1067
+ },
1068
+ {
1069
+ id: "music",
1070
+ method: "POST",
1071
+ hostedPath: "/voice/op/music",
1072
+ legacyHostedPath: "/voice/music",
1073
+ upstreamPath: "/v1/music",
1074
+ transport: "json-binary",
1075
+ call: "composeMusic",
1076
+ permission: "project:read",
1077
+ maxBodyBytes: MAX_JSON_BYTES,
1078
+ cost: { unit: "second", per: 1 },
1079
+ models: [DEFAULT_MUSIC_MODEL],
1080
+ defaultModel: DEFAULT_MUSIC_MODEL,
1081
+ forward: {
1082
+ prompt: "prompt",
1083
+ compositionPlan: "composition_plan",
1084
+ musicLengthMs: "music_length_ms",
1085
+ modelId: "model_id",
1086
+ forceInstrumental: "force_instrumental",
1087
+ outputFormat: "output_format"
1088
+ },
1089
+ accept: "audio/mpeg",
1090
+ errorLabel: "Music generation failed",
1091
+ validate: (fields) => {
1092
+ const hasPrompt = typeof fields.prompt === "string" && fields.prompt.trim().length > 0;
1093
+ const hasPlan = fields.compositionPlan != null && typeof fields.compositionPlan === "object";
1094
+ return hasPrompt === hasPlan ? "Provide exactly one of `prompt` or `compositionPlan`" : null;
1095
+ }
1096
+ },
1097
+ {
1098
+ id: "voices",
1099
+ method: "GET",
1100
+ hostedPath: "/voice/op/voices",
1101
+ upstreamPath: "/v1/voices",
1102
+ transport: "json-json",
1103
+ permission: "project:read",
1104
+ maxBodyBytes: 0,
1105
+ cost: { unit: "request", per: 1 },
1106
+ queryForward: ["page_size", "search", "voice_type", "category", "next_page_token"],
1107
+ accept: "application/json"
1108
+ },
1109
+ {
1110
+ id: "models",
1111
+ method: "GET",
1112
+ hostedPath: "/voice/op/models",
1113
+ upstreamPath: "/v1/models",
1114
+ transport: "json-json",
1115
+ permission: "project:read",
1116
+ maxBodyBytes: 0,
1117
+ cost: { unit: "request", per: 1 },
1118
+ accept: "application/json"
1119
+ }
1120
+ ];
1121
+ var VOICE_OP_IDS = VOICE_OPS.map((op) => op.id);
1122
+ function getVoiceOp(id) {
1123
+ if (!id) return void 0;
1124
+ return VOICE_OPS.find((op) => op.id === id);
1125
+ }
1126
+ function voiceOpForwardPath(op) {
1127
+ return op.legacyHostedPath ?? op.hostedPath;
1128
+ }
1129
+ function voiceOpIsMultipart(op) {
1130
+ return op.transport === "multipart-binary" || op.transport === "multipart-json";
1131
+ }
1132
+ function voiceOpReturnsAudio(op) {
1133
+ return op.transport === "json-binary" || op.transport === "multipart-binary";
1134
+ }
1135
+ function voiceOpUpstreamPath(op, params) {
1136
+ let path = op.upstreamPath;
1137
+ for (const name of op.pathParams ?? []) {
1138
+ const value = params[name];
1139
+ if (!value) {
1140
+ return { error: `Missing \`${name}\` (required path parameter for ${op.id})` };
1141
+ }
1142
+ path = path.split(`{${name}}`).join(encodeURIComponent(value));
1143
+ }
1144
+ return path;
1145
+ }
1146
+ function voiceOpCredentialError(op, credential) {
1147
+ if (op.byoOnly && credential === "pooled") {
1148
+ return {
1149
+ status: 403,
1150
+ error: "byo_key_required",
1151
+ detail: `${op.id} runs on your own ElevenLabs key, not Shogo's pooled key \u2014 pass it to ElevenLabsClient and call request() directly`
1152
+ };
1153
+ }
1154
+ return null;
1155
+ }
1156
+ var MODEL_FIELD = "modelId";
1157
+ function parseVoiceOpJsonBody(op, raw) {
1158
+ if (raw === null || typeof raw !== "object" || Array.isArray(raw)) {
1159
+ return { ok: false, status: 400, error: "Invalid JSON body" };
1160
+ }
1161
+ const body = raw;
1162
+ const fields = {};
1163
+ for (const [callerField, upstreamField] of Object.entries(op.forward ?? {})) {
1164
+ if (upstreamField && body[callerField] !== void 0) fields[callerField] = body[callerField];
1165
+ }
1166
+ for (const name of op.required ?? []) {
1167
+ const value = fields[name];
1168
+ if (value === void 0 || value === null || typeof value === "string" && !value.trim()) {
1169
+ return { ok: false, status: 400, error: `Missing \`${name}\`` };
1170
+ }
1171
+ }
1172
+ const modelCheck = checkVoiceOpModel(op, fields);
1173
+ if ("error" in modelCheck) return { ok: false, status: 400, ...modelCheck };
1174
+ if (op.validate) {
1175
+ const message = op.validate(fields);
1176
+ if (message) return { ok: false, status: 400, error: message };
1177
+ }
1178
+ return { ok: true, fields };
1179
+ }
1180
+ function parseVoiceOpForm(op, form) {
1181
+ const fields = {};
1182
+ for (const callerField of Object.keys(op.forward ?? {})) {
1183
+ const value = form.get(callerField);
1184
+ if (value !== null) fields[callerField] = value;
1185
+ }
1186
+ for (const name of op.required ?? []) {
1187
+ const value = fields[name];
1188
+ if (!(value instanceof Blob)) {
1189
+ return {
1190
+ ok: false,
1191
+ status: 400,
1192
+ error: `Missing \`${name}\` file part`,
1193
+ detail: value === void 0 ? `send a multipart body with a \`${name}\` file part` : `\`${name}\` must be a file part, not a text field`
1194
+ };
1195
+ }
1196
+ if (value.size === 0) {
1197
+ return { ok: false, status: 400, error: `Empty \`${name}\` file part` };
1198
+ }
1199
+ }
1200
+ const modelCheck = checkVoiceOpModel(op, fields);
1201
+ if ("error" in modelCheck) return { ok: false, status: 400, ...modelCheck };
1202
+ const forward = op.forward ?? {};
1203
+ const pathParams = new Set(op.pathParams ?? []);
1204
+ const out = new FormData();
1205
+ for (const [callerField, upstreamField] of Object.entries(forward)) {
1206
+ if (pathParams.has(callerField)) continue;
1207
+ const value = fields[callerField];
1208
+ if (value === void 0 || value === null || value === "") continue;
1209
+ if (value instanceof Blob) {
1210
+ const name = value instanceof File ? value.name : callerField;
1211
+ out.set(upstreamField, value, name);
1212
+ } else if (typeof value === "string") {
1213
+ out.set(upstreamField, value);
1214
+ } else {
1215
+ out.set(upstreamField, JSON.stringify(value));
1216
+ }
1217
+ }
1218
+ const modelField = forward[MODEL_FIELD];
1219
+ if (op.defaultModel && modelField && out.get(modelField) === null) {
1220
+ out.set(modelField, op.defaultModel);
1221
+ }
1222
+ return { ok: true, form: out, fields };
1223
+ }
1224
+ function voiceOpUpstreamBody(op, fields) {
1225
+ const pathParams = new Set(op.pathParams ?? []);
1226
+ const body = {};
1227
+ for (const [callerField, upstreamField] of Object.entries(op.forward ?? {})) {
1228
+ if (pathParams.has(callerField)) continue;
1229
+ const value = fields[callerField];
1230
+ if (value === void 0 || value === null) continue;
1231
+ body[upstreamField] = value;
1232
+ }
1233
+ return body;
1234
+ }
1235
+ function voiceOpPathParams(op, fields, query) {
1236
+ const out = {};
1237
+ for (const name of op.pathParams ?? []) {
1238
+ const fromBody = fields[name];
1239
+ const value = typeof fromBody === "string" && fromBody.length > 0 ? fromBody : query.get(name);
1240
+ if (value) out[name] = value;
1241
+ }
1242
+ return out;
1243
+ }
1244
+ function checkVoiceOpModel(op, fields) {
1245
+ const requested = fields[MODEL_FIELD];
1246
+ if (requested === void 0 || requested === null || requested === "") return { ok: true };
1247
+ if (typeof requested !== "string") {
1248
+ return { error: `\`${MODEL_FIELD}\` must be a string` };
1249
+ }
1250
+ if (op.models && !op.models.includes(requested)) {
1251
+ return {
1252
+ error: "unknown_model",
1253
+ detail: `Unknown model '${requested}' for ${op.id}`,
1254
+ allowed: op.models
1255
+ };
1256
+ }
1257
+ return { ok: true };
1258
+ }
1259
+
720
1260
  // src/twilio.ts
721
1261
  var DEFAULT_TWILIO_BASE_URL = "https://api.twilio.com";
722
1262
  var TwilioApiError = class extends Error {
@@ -1377,6 +1917,149 @@ function isVoiceMockEnv() {
1377
1917
  const v = value.toLowerCase();
1378
1918
  return v === "mock" || v === "demo" || v === "fake";
1379
1919
  }
1920
+
1921
+ // src/live/client.ts
1922
+ function encodeAudio(audio) {
1923
+ if (typeof btoa === "function") {
1924
+ let binary = "";
1925
+ for (let i = 0; i < audio.length; i += 32768) {
1926
+ binary += String.fromCharCode(...audio.subarray(i, i + 32768));
1927
+ }
1928
+ return btoa(binary);
1929
+ }
1930
+ const BufferImpl = globalThis.Buffer;
1931
+ if (BufferImpl) return BufferImpl.from(audio).toString("base64");
1932
+ throw new Error("Base64 encoding is unavailable in this runtime");
1933
+ }
1934
+ function normalizeBaseUrl(value) {
1935
+ return value.replace(/\/+$/, "");
1936
+ }
1937
+ var LiveSession = class {
1938
+ constructor(socket, initialSession) {
1939
+ this.socket = socket;
1940
+ this.initialSession = initialSession;
1941
+ this.handlers = /* @__PURE__ */ new Map();
1942
+ this.closed = false;
1943
+ this.latestUsageSeconds = 0;
1944
+ this.startedPromise = new Promise((resolve, reject) => {
1945
+ this.resolveStarted = resolve;
1946
+ this.rejectStarted = reject;
1947
+ });
1948
+ this.started = this.startedPromise;
1949
+ this.closedPromise = new Promise((resolve) => {
1950
+ this.resolveClosed = resolve;
1951
+ });
1952
+ socket.addEventListener("message", (message) => {
1953
+ const raw = typeof message.data === "string" ? message.data : "";
1954
+ if (!raw) return;
1955
+ let event;
1956
+ try {
1957
+ event = JSON.parse(raw);
1958
+ } catch {
1959
+ return;
1960
+ }
1961
+ if (event.type === "session.started") this.resolveStarted(event);
1962
+ if (event.type === "session.usage.updated") {
1963
+ const seconds = event.usage?.seconds;
1964
+ if (typeof seconds === "number") this.latestUsageSeconds = seconds;
1965
+ }
1966
+ if (event.type === "session.closed") {
1967
+ this.closed = true;
1968
+ this.resolveClosed(event);
1969
+ }
1970
+ if (event.type === "error" && !this.closed) {
1971
+ const messageText = String(event.error?.message || "Live session error");
1972
+ this.rejectStarted(new Error(messageText));
1973
+ }
1974
+ for (const handler of this.handlers.get(String(event.type)) ?? []) handler(event);
1975
+ for (const handler of this.handlers.get("*") ?? []) handler(event);
1976
+ });
1977
+ socket.addEventListener("close", () => {
1978
+ if (!this.closed) this.resolveClosed();
1979
+ });
1980
+ socket.addEventListener("error", () => {
1981
+ if (!this.closed) this.rejectStarted(new Error("Live session WebSocket error"));
1982
+ });
1983
+ socket.addEventListener("open", () => {
1984
+ if (initialSession) this.send({ type: "session.start", session: initialSession });
1985
+ });
1986
+ }
1987
+ on(eventType, handler) {
1988
+ const handlers = this.handlers.get(eventType) ?? /* @__PURE__ */ new Set();
1989
+ handlers.add(handler);
1990
+ this.handlers.set(eventType, handlers);
1991
+ return () => handlers.delete(handler);
1992
+ }
1993
+ send(event) {
1994
+ if (this.closed) throw new Error("Live session is closed");
1995
+ this.socket.send(JSON.stringify(event));
1996
+ }
1997
+ appendAudio(audio) {
1998
+ this.send({
1999
+ type: "session.input_audio.append",
2000
+ audio: typeof audio === "string" ? audio : encodeAudio(audio)
2001
+ });
2002
+ }
2003
+ async close(timeoutMs = 15e3) {
2004
+ if (this.closed) return;
2005
+ if (this.socket.readyState === 1) this.send({ type: "session.close" });
2006
+ const timeout = new Promise((resolve) => setTimeout(resolve, timeoutMs));
2007
+ const result = await Promise.race([this.closedPromise, timeout]);
2008
+ if (!this.closed) this.socket.close();
2009
+ return result;
2010
+ }
2011
+ };
2012
+ var ShogoLiveClient = class {
2013
+ constructor(options) {
2014
+ if (!!options.apiKey === !!options.runtimeToken) {
2015
+ throw new Error("ShogoLiveClient requires exactly one of apiKey or runtimeToken.");
2016
+ }
2017
+ this.apiUrl = normalizeBaseUrl(options.apiUrl);
2018
+ this.token = options.apiKey ?? options.runtimeToken;
2019
+ this.fetchImpl = options.fetch ?? globalThis.fetch;
2020
+ this.WebSocketImpl = options.WebSocket ?? globalThis.WebSocket;
2021
+ if (typeof this.fetchImpl !== "function") throw new Error("fetch is unavailable");
2022
+ if (typeof this.WebSocketImpl !== "function") throw new Error("WebSocket is unavailable");
2023
+ }
2024
+ httpUrl(path) {
2025
+ return `${this.apiUrl}${path}`;
2026
+ }
2027
+ wsUrl(path) {
2028
+ return this.httpUrl(path).replace(/^http/, "ws");
2029
+ }
2030
+ protocols() {
2031
+ return ["shogo-live", `shogo-insecure-api-key.${this.token}`];
2032
+ }
2033
+ connect(session) {
2034
+ const socket = new this.WebSocketImpl(this.wsUrl("/api/ai/v1/live/sessions"), this.protocols());
2035
+ return new LiveSession(socket, session);
2036
+ }
2037
+ attach(sessionId) {
2038
+ const socket = new this.WebSocketImpl(
2039
+ this.wsUrl(`/api/ai/v1/live/sessions/${encodeURIComponent(sessionId)}/attach`),
2040
+ this.protocols()
2041
+ );
2042
+ return new LiveSession(socket);
2043
+ }
2044
+ async createWebRtcSession(input) {
2045
+ const response = await this.fetchImpl(this.httpUrl("/api/ai/v1/live/sessions"), {
2046
+ method: "POST",
2047
+ headers: {
2048
+ Authorization: `Bearer ${this.token}`,
2049
+ "Content-Type": "application/json"
2050
+ },
2051
+ body: JSON.stringify({
2052
+ session: input.session,
2053
+ transport: { type: "webrtc", sdp: input.sdp }
2054
+ })
2055
+ });
2056
+ const body = await response.json();
2057
+ if (!response.ok) {
2058
+ throw new Error(body?.error?.message || `Live session creation failed (${response.status})`);
2059
+ }
2060
+ return body;
2061
+ }
2062
+ };
1380
2063
  // Annotate the CommonJS export names for ESM import in node:
1381
2064
  0 && (module.exports = {
1382
2065
  AUDIO_TAGS,
@@ -1387,6 +2070,10 @@ function isVoiceMockEnv() {
1387
2070
  DEFAULT_ELEVENLABS_BASE_URL,
1388
2071
  DEFAULT_MEMORY_BLOCK,
1389
2072
  DEFAULT_MUSIC_MODEL,
2073
+ DEFAULT_SOUND_GENERATION_MODEL,
2074
+ DEFAULT_SPEECH_TO_TEXT_MODEL,
2075
+ DEFAULT_STS_MODEL,
2076
+ DEFAULT_TTS_MODEL,
1390
2077
  DEFAULT_TWILIO_BASE_URL,
1391
2078
  DEFAULT_VOICE_SETTINGS,
1392
2079
  DirectTelephonyClient,
@@ -1397,27 +2084,50 @@ function isVoiceMockEnv() {
1397
2084
  ElevenLabsClient,
1398
2085
  HostedRuntimeTokenClient,
1399
2086
  HostedTelephonyClient,
2087
+ LiveSession,
1400
2088
  MEMORY_CLIENT_TOOLS,
1401
2089
  MockTelephonyClient,
2090
+ ShogoLiveClient,
2091
+ TTS_FALLBACK_MODEL,
1402
2092
  TelephonyApiError,
1403
2093
  TelephonyConfigError,
1404
2094
  TwilioApiError,
1405
2095
  TwilioClient,
2096
+ VOICE_OPS,
2097
+ VOICE_OP_IDS,
2098
+ VOICE_SOUND_GENERATION_MODEL_IDS,
2099
+ VOICE_SPEECH_TO_TEXT_MODEL_IDS,
2100
+ VOICE_STS_MODEL_IDS,
2101
+ VOICE_TTS_MODELS,
2102
+ VOICE_TTS_MODEL_IDS,
1406
2103
  buildPreviewLine,
1407
2104
  composeAgentPrompt,
1408
2105
  composeExpressivityBlock,
1409
2106
  createTelephonyClient,
1410
2107
  extractBasePrompt,
2108
+ getVoiceOp,
2109
+ getVoiceTtsModel,
2110
+ isModelRelatedTtsFailure,
1411
2111
  isVoiceMockEnv,
1412
2112
  normalizeAudioTags,
1413
2113
  normalizeExpressivity,
1414
2114
  normalizeVoiceSettings,
2115
+ parseVoiceOpForm,
2116
+ parseVoiceOpJsonBody,
1415
2117
  readAudioTags,
1416
2118
  readExpressivity,
1417
2119
  readVoiceSettings,
1418
2120
  resolveConvaiTtsModel,
2121
+ resolveConvaiTtsModelDetailed,
1419
2122
  stripAudioTags,
1420
2123
  stripExpressivityBlock,
1421
- stripMemoryBlock
2124
+ stripMemoryBlock,
2125
+ voiceOpCredentialError,
2126
+ voiceOpForwardPath,
2127
+ voiceOpIsMultipart,
2128
+ voiceOpPathParams,
2129
+ voiceOpReturnsAudio,
2130
+ voiceOpUpstreamBody,
2131
+ voiceOpUpstreamPath
1422
2132
  });
1423
2133
  //# sourceMappingURL=index.cjs.map