@shogo-ai/voice 1.10.0 → 2.4.2

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