@shogo-ai/voice 1.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 (48) hide show
  1. package/LICENSE +21 -0
  2. package/README.md +30 -0
  3. package/dist/chunk-D2EVSUZX.js +27 -0
  4. package/dist/chunk-Q3TJAJ54.js +165 -0
  5. package/dist/chunk-QHDIEE7O.js +453 -0
  6. package/dist/chunk-S3S2RNO6.js +671 -0
  7. package/dist/chunk-ZGYHDC5G.js +423 -0
  8. package/dist/index.cjs +1316 -0
  9. package/dist/index.d.cts +432 -0
  10. package/dist/index.d.ts +432 -0
  11. package/dist/index.js +735 -0
  12. package/dist/native/index.cjs +1667 -0
  13. package/dist/native/index.d.cts +285 -0
  14. package/dist/native/index.d.ts +285 -0
  15. package/dist/native/index.js +949 -0
  16. package/dist/organicParticlesShaders-DIva0vBO.d.cts +693 -0
  17. package/dist/organicParticlesShaders-DIva0vBO.d.ts +693 -0
  18. package/dist/react/index.cjs +1670 -0
  19. package/dist/react/index.d.cts +423 -0
  20. package/dist/react/index.d.ts +423 -0
  21. package/dist/react/index.js +952 -0
  22. package/dist/route/agent.cjs +981 -0
  23. package/dist/route/agent.d.cts +20 -0
  24. package/dist/route/agent.d.ts +20 -0
  25. package/dist/route/agent.js +17 -0
  26. package/dist/route/audio-tags.cjs +975 -0
  27. package/dist/route/audio-tags.d.cts +16 -0
  28. package/dist/route/audio-tags.d.ts +16 -0
  29. package/dist/route/audio-tags.js +13 -0
  30. package/dist/route/index.cjs +987 -0
  31. package/dist/route/index.d.cts +115 -0
  32. package/dist/route/index.d.ts +115 -0
  33. package/dist/route/index.js +12 -0
  34. package/dist/route/signed-url.cjs +975 -0
  35. package/dist/route/signed-url.d.cts +12 -0
  36. package/dist/route/signed-url.d.ts +12 -0
  37. package/dist/route/signed-url.js +13 -0
  38. package/dist/route/tts-preview.cjs +975 -0
  39. package/dist/route/tts-preview.d.cts +17 -0
  40. package/dist/route/tts-preview.d.ts +17 -0
  41. package/dist/route/tts-preview.js +13 -0
  42. package/dist/server.cjs +999 -0
  43. package/dist/server.d.cts +88 -0
  44. package/dist/server.d.ts +88 -0
  45. package/dist/server.js +11 -0
  46. package/dist/types-BKJtOUbE.d.cts +352 -0
  47. package/dist/types-BKJtOUbE.d.ts +352 -0
  48. package/package.json +148 -0
@@ -0,0 +1,975 @@
1
+ "use strict";
2
+ var __defProp = Object.defineProperty;
3
+ var __getOwnPropDesc = Object.getOwnPropertyDescriptor;
4
+ var __getOwnPropNames = Object.getOwnPropertyNames;
5
+ var __hasOwnProp = Object.prototype.hasOwnProperty;
6
+ var __export = (target, all) => {
7
+ for (var name in all)
8
+ __defProp(target, name, { get: all[name], enumerable: true });
9
+ };
10
+ var __copyProps = (to, from, except, desc) => {
11
+ if (from && typeof from === "object" || typeof from === "function") {
12
+ for (let key of __getOwnPropNames(from))
13
+ if (!__hasOwnProp.call(to, key) && key !== except)
14
+ __defProp(to, key, { get: () => from[key], enumerable: !(desc = __getOwnPropDesc(from, key)) || desc.enumerable });
15
+ }
16
+ return to;
17
+ };
18
+ var __toCommonJS = (mod) => __copyProps(__defProp({}, "__esModule", { value: true }), mod);
19
+
20
+ // src/route/audio-tags.ts
21
+ var audio_tags_exports = {};
22
+ __export(audio_tags_exports, {
23
+ GET: () => GET
24
+ });
25
+ module.exports = __toCommonJS(audio_tags_exports);
26
+
27
+ // src/audioTags.ts
28
+ var AUDIO_TAGS = [
29
+ // Emotions
30
+ { tag: "happy", group: "emotion", label: "Happy", description: "Warm, upbeat delivery.", example: "[happy] Oh, I\u2019m so glad you stopped by." },
31
+ { tag: "sad", group: "emotion", label: "Sad", description: "Subdued, heavy-hearted tone.", example: "[sad] I wish things had gone differently." },
32
+ { tag: "excited", group: "emotion", label: "Excited", description: "Energized, eager delivery.", example: "[excited] Wait until you hear this!" },
33
+ { tag: "curious", group: "emotion", label: "Curious", description: "Inquisitive, rising inflection.", example: "[curious] Hmm, what did they look like?" },
34
+ { tag: "sarcastic", group: "emotion", label: "Sarcastic", description: "Dry, ironic tone.", example: "[sarcastic] Oh, fantastic. Another Monday." },
35
+ { tag: "angry", group: "emotion", label: "Angry", description: "Sharp, irritated edge.", example: "[angry] I told you not to touch that." },
36
+ { tag: "nervous", group: "emotion", label: "Nervous", description: "Uneasy, faltering delivery.", example: "[nervous] I\u2014 I didn\u2019t think you\u2019d actually come." },
37
+ { tag: "confident", group: "emotion", label: "Confident", description: "Grounded, assured delivery.", example: "[confident] Trust me. I\u2019ve done this before." },
38
+ // Delivery
39
+ { tag: "whispers", group: "delivery", label: "Whispers", description: "Hushed, close-mic delivery.", example: "[whispers] Don\u2019t say it too loud." },
40
+ { tag: "shouts", group: "delivery", label: "Shouts", description: "Loud, projected delivery.", example: "[shouts] Look out behind you!" },
41
+ { tag: "softly", group: "delivery", label: "Softly", description: "Gentle, quiet tone.", example: "[softly] It\u2019s okay. I\u2019m here." },
42
+ { tag: "slowly", group: "delivery", label: "Slowly", description: "Deliberate pacing.", example: "[slowly] Tell me\u2026 exactly\u2026 what happened." },
43
+ { tag: "quickly", group: "delivery", label: "Quickly", description: "Rapid, urgent pacing.", example: "[quickly] We need to go, right now." },
44
+ // Reactions (non-verbal sounds)
45
+ { tag: "laughs", group: "reaction", label: "Laughs", description: "A genuine laugh.", example: "That\u2019s ridiculous [laughs]." },
46
+ { tag: "chuckles", group: "reaction", label: "Chuckles", description: "A small, warm chuckle.", example: "[chuckles] You always say that." },
47
+ { tag: "sighs", group: "reaction", label: "Sighs", description: "An audible sigh.", example: "[sighs] Alright, fine. Let\u2019s do it." },
48
+ { tag: "gasps", group: "reaction", label: "Gasps", description: "A sharp inhale of surprise.", example: "[gasps] You didn\u2019t!" },
49
+ { tag: "hesitates", group: "reaction", label: "Hesitates", description: "A pause with uncertainty.", example: "[hesitates] I\u2019m not sure I should say." },
50
+ { tag: "clears throat", group: "reaction", label: "Clears throat", description: "Throat-clearing beat.", example: "[clears throat] As I was saying\u2026" }
51
+ ];
52
+ var AUDIO_TAG_GROUPS = [
53
+ { id: "emotion", label: "Emotions", description: "Overall feeling carried through the line." },
54
+ { id: "delivery", label: "Delivery", description: "How the words are spoken (pace, volume)." },
55
+ { id: "reaction", label: "Reactions", description: "Non-verbal sounds mixed into speech." }
56
+ ];
57
+ var EXPRESSIVITY_OPTIONS = [
58
+ { id: "off", label: "Off", description: "No audio tags. Plain, natural delivery." },
59
+ { id: "subtle", label: "Subtle", description: "Occasional tag when it genuinely fits. Default." },
60
+ { id: "full", label: "Full", description: "Freely expressive \u2014 tags sprinkled to carry emotion and reactions." }
61
+ ];
62
+ var DEFAULT_ALLOWED_TAGS = [
63
+ "whispers",
64
+ "laughs",
65
+ "sighs",
66
+ "excited",
67
+ "curious",
68
+ "softly"
69
+ ];
70
+ var DEFAULT_VOICE_SETTINGS = {
71
+ stability: 0.5,
72
+ similarity_boost: 0.8,
73
+ style: 0,
74
+ use_speaker_boost: true
75
+ };
76
+ var KNOWN_TAG_SET = new Set(AUDIO_TAGS.map((t) => t.tag.toLowerCase()));
77
+ var EXPRESSIVITY_BLOCK_OPEN = "<!--expressivity-->";
78
+ var EXPRESSIVITY_BLOCK_CLOSE = "<!--/expressivity-->";
79
+ function stripExpressivityBlock(prompt) {
80
+ if (!prompt) return prompt;
81
+ const open = EXPRESSIVITY_BLOCK_OPEN;
82
+ const close = EXPRESSIVITY_BLOCK_CLOSE;
83
+ const openIdx = prompt.indexOf(open);
84
+ if (openIdx === -1) return prompt;
85
+ const closeIdx = prompt.indexOf(close, openIdx);
86
+ if (closeIdx === -1) return prompt;
87
+ const before = prompt.slice(0, openIdx).replace(/\s+$/, "");
88
+ const after = prompt.slice(closeIdx + close.length).replace(/^\s+/, "");
89
+ return [before, after].filter(Boolean).join("\n\n");
90
+ }
91
+ function composeExpressivityBlock(expressivity, allowedTags) {
92
+ if (expressivity === "off") return "";
93
+ const allowed = (allowedTags && allowedTags.length > 0 ? allowedTags : DEFAULT_ALLOWED_TAGS).filter((t) => AUDIO_TAGS.some((a) => a.tag === t));
94
+ if (allowed.length === 0) return "";
95
+ const intensity = expressivity === "full" ? "Use audio tags liberally to color your speech with emotion, delivery, and non-verbal reactions. Most substantive replies should include at least one tag. Never add so many tags that a line becomes unnatural." : "Use audio tags sparingly \u2014 only when one genuinely fits the emotion or reaction of the moment. Most lines will have no tag. At most one or two tags per reply.";
96
+ const tagList = allowed.map((t) => {
97
+ const entry = AUDIO_TAGS.find((a) => a.tag === t);
98
+ return ` - [${entry.tag}] \u2014 ${entry.description} Example: ${entry.example}`;
99
+ }).join("\n");
100
+ const body = [
101
+ "You speak through an expressive TTS engine that understands inline audio tags.",
102
+ 'Audio tags are lowercase words in square brackets placed directly in your spoken text \u2014 e.g. "[whispers] don\u2019t tell anyone" or "that was wild [laughs]".',
103
+ 'Tags are performance cues, not words. Never describe a tag (do not say "asterisk laughs asterisk" or "*laughs*"). Never read a tag aloud. Never put tags inside quoted speech from another character.',
104
+ intensity,
105
+ "You may only use tags from this allow-list:",
106
+ tagList,
107
+ "If no tag fits, use none. Tags are optional flavor, not filler."
108
+ ].join("\n");
109
+ return `${EXPRESSIVITY_BLOCK_OPEN}
110
+ ${body}
111
+ ${EXPRESSIVITY_BLOCK_CLOSE}`;
112
+ }
113
+ function buildPreviewLine(allowedTags, characterName = "your companion") {
114
+ const tags = (allowedTags ?? []).filter((t) => AUDIO_TAGS.some((a) => a.tag === t));
115
+ if (tags.length === 0) {
116
+ return `Hi there. This is a quick preview of how ${characterName} sounds.`;
117
+ }
118
+ const pick = tags.slice(0, 3);
119
+ const lead = pick[0];
120
+ const mid = pick[1];
121
+ const tail = pick[2];
122
+ if (pick.length === 1) {
123
+ return `[${lead}] Hey \u2014 this is how ${characterName} sounds when the mood takes over.`;
124
+ }
125
+ if (pick.length === 2) {
126
+ return `[${lead}] Oh, this is fun. [${mid}] Let me know what you think.`;
127
+ }
128
+ return `[${lead}] Okay, listen. [${mid}] This is the new voice. [${tail}] How does it feel?`;
129
+ }
130
+ function normalizeAudioTags(raw) {
131
+ if (!Array.isArray(raw)) return null;
132
+ const cleaned = raw.filter((x) => typeof x === "string").map((x) => x.trim().toLowerCase()).filter((x) => AUDIO_TAGS.some((a) => a.tag === x));
133
+ return Array.from(new Set(cleaned));
134
+ }
135
+ function normalizeExpressivity(raw) {
136
+ if (raw === "off" || raw === "subtle" || raw === "full") return raw;
137
+ return void 0;
138
+ }
139
+ function normalizeVoiceSettings(raw) {
140
+ if (!raw || typeof raw !== "object") return void 0;
141
+ const src = raw;
142
+ const out = {};
143
+ const clamp = (v) => typeof v === "number" && Number.isFinite(v) ? Math.min(1, Math.max(0, v)) : void 0;
144
+ const stability = clamp(src.stability);
145
+ const similarity = clamp(src.similarity_boost ?? src.similarityBoost);
146
+ const style = clamp(src.style);
147
+ const boost = src.use_speaker_boost ?? src.useSpeakerBoost;
148
+ if (stability !== void 0) out.stability = stability;
149
+ if (similarity !== void 0) out.similarity_boost = similarity;
150
+ if (style !== void 0) out.style = style;
151
+ if (typeof boost === "boolean") out.use_speaker_boost = boost;
152
+ return Object.keys(out).length ? out : void 0;
153
+ }
154
+ function readAudioTags(raw) {
155
+ const norm = normalizeAudioTags(raw);
156
+ return norm && norm.length > 0 ? norm : DEFAULT_ALLOWED_TAGS;
157
+ }
158
+ function readExpressivity(raw) {
159
+ return normalizeExpressivity(raw) ?? "subtle";
160
+ }
161
+ function readVoiceSettings(raw) {
162
+ return normalizeVoiceSettings(raw) ?? { ...DEFAULT_VOICE_SETTINGS };
163
+ }
164
+
165
+ // src/prompt.ts
166
+ var DEFAULT_MEMORY_BLOCK = `# Memory
167
+
168
+ Known context about this user from past conversations:
169
+ {{user_context}}
170
+
171
+ Memory retrieval is automatic \u2014 the top matching facts are already injected above. Never say "let me check my memory" or "searching memories."
172
+
173
+ You have one tool:
174
+ - add_memory(fact): save a concise, canonical fact about the user \u2014 preferences, decisions, personal details, follow-ups. Use short normalized phrasings like "favorite color: green" or "lives in Honolulu". Always call it when the user explicitly asks you to remember something.`;
175
+ function stripMemoryBlock(prompt) {
176
+ if (!prompt) return prompt;
177
+ return prompt.replace(/\n*<!-- zix-memory-v\d+ -->[\s\S]*$/m, "").replace(/\n*# Memory\s*\n\s*Known context about this user[\s\S]*$/m, "").trimEnd();
178
+ }
179
+ function extractBasePrompt(prompt) {
180
+ return stripExpressivityBlock(stripMemoryBlock(prompt));
181
+ }
182
+ function composeAgentPrompt(basePrompt, options = {}) {
183
+ const { expressivity = "subtle", audioTags = null, memoryBlock = DEFAULT_MEMORY_BLOCK } = options;
184
+ const stripped = extractBasePrompt(basePrompt);
185
+ const expr = composeExpressivityBlock(expressivity, audioTags);
186
+ const parts = [stripped];
187
+ if (expr) parts.push(expr);
188
+ if (memoryBlock) parts.push(memoryBlock);
189
+ return parts.join("\n\n");
190
+ }
191
+
192
+ // src/elevenlabs.ts
193
+ var DEFAULT_ELEVENLABS_BASE_URL = "https://api.elevenlabs.io";
194
+ var CONVAI_SUPPORTED_TTS_MODELS = /* @__PURE__ */ new Set([
195
+ "eleven_turbo_v2",
196
+ "eleven_turbo_v2_5",
197
+ "eleven_flash_v2",
198
+ "eleven_flash_v2_5",
199
+ "eleven_multilingual_v2"
200
+ ]);
201
+ var CONVAI_TTS_MODEL_FALLBACK = "eleven_turbo_v2_5";
202
+ function resolveConvaiTtsModel(requested) {
203
+ if (requested && CONVAI_SUPPORTED_TTS_MODELS.has(requested)) return requested;
204
+ return CONVAI_TTS_MODEL_FALLBACK;
205
+ }
206
+ var ElevenLabsApiError = class extends Error {
207
+ constructor(message, status, body) {
208
+ super(message);
209
+ this.status = status;
210
+ this.body = body;
211
+ this.name = "ElevenLabsApiError";
212
+ }
213
+ };
214
+ var ElevenLabsClient = class {
215
+ constructor(config) {
216
+ this.config = config;
217
+ if (!config.apiKey) throw new Error("ElevenLabsClient: apiKey is required");
218
+ this.baseUrl = (config.baseUrl ?? DEFAULT_ELEVENLABS_BASE_URL).replace(/\/+$/, "");
219
+ this.fetchImpl = config.fetch ?? globalThis.fetch;
220
+ if (typeof this.fetchImpl !== "function") {
221
+ throw new Error("ElevenLabsClient: global fetch is unavailable; pass config.fetch");
222
+ }
223
+ }
224
+ headers(extra = {}) {
225
+ return {
226
+ "xi-api-key": this.config.apiKey,
227
+ ...extra
228
+ };
229
+ }
230
+ /** Create a new convai agent. Returns the new `agent_id`. */
231
+ async createAgent(params) {
232
+ const prompt = composeAgentPrompt(params.systemPrompt, {
233
+ expressivity: params.expressivity ?? "subtle",
234
+ audioTags: params.audioTags ?? null,
235
+ memoryBlock: params.memoryBlock
236
+ });
237
+ const ttsConfig = {
238
+ voice_id: params.voiceId,
239
+ model_id: resolveConvaiTtsModel(params.ttsModelId)
240
+ };
241
+ if (params.voiceSettings) ttsConfig.voice_settings = params.voiceSettings;
242
+ const body = {
243
+ name: `Companion-${params.characterName}-for-${params.displayName}`,
244
+ conversation_config: {
245
+ agent: {
246
+ prompt: {
247
+ prompt,
248
+ ...params.tools ? { tools: params.tools } : {}
249
+ },
250
+ first_message: params.firstMessage,
251
+ language: params.language ?? "en"
252
+ },
253
+ tts: ttsConfig
254
+ },
255
+ platform_settings: {
256
+ overrides: {
257
+ conversation_config_override: {
258
+ agent: { prompt: { prompt: true }, first_message: true, language: true },
259
+ tts: { voice_id: true },
260
+ conversation: { text_only: true }
261
+ }
262
+ },
263
+ dynamic_variables: {
264
+ dynamic_variable_placeholders: {
265
+ user_context: "No prior memories yet.",
266
+ character_name: "",
267
+ ...params.dynamicVariablePlaceholders ?? {}
268
+ }
269
+ }
270
+ }
271
+ };
272
+ const res = await this.fetchImpl(`${this.baseUrl}/v1/convai/agents/create`, {
273
+ method: "POST",
274
+ headers: this.headers({ "content-type": "application/json" }),
275
+ body: JSON.stringify(body)
276
+ });
277
+ if (!res.ok) {
278
+ const text = await res.text();
279
+ throw new ElevenLabsApiError(`createAgent failed: ${res.status}`, res.status, text);
280
+ }
281
+ const data = await res.json();
282
+ return data.agent_id;
283
+ }
284
+ /** PATCH any subset of agent settings. Idempotent; only sends the keys you supply. */
285
+ async patchAgent(agentId, params) {
286
+ const body = {};
287
+ if (params.displayName !== void 0 || params.characterName !== void 0) {
288
+ body.name = `Companion-${params.characterName ?? ""}-for-${params.displayName ?? ""}`.replace(
289
+ /-for-$/,
290
+ ""
291
+ );
292
+ }
293
+ const conv = {};
294
+ const agent = {};
295
+ const prompt = {};
296
+ if (params.systemPrompt !== void 0) {
297
+ prompt.prompt = composeAgentPrompt(params.systemPrompt, {
298
+ expressivity: params.expressivity ?? "subtle",
299
+ audioTags: params.audioTags ?? null,
300
+ memoryBlock: params.memoryBlock
301
+ });
302
+ }
303
+ if (params.tools) prompt.tools = params.tools;
304
+ if (Object.keys(prompt).length) agent.prompt = prompt;
305
+ if (params.firstMessage !== void 0) agent.first_message = params.firstMessage;
306
+ if (Object.keys(agent).length) conv.agent = agent;
307
+ const tts = {};
308
+ if (params.voiceId !== void 0) tts.voice_id = params.voiceId;
309
+ if (params.voiceSettings) tts.voice_settings = params.voiceSettings;
310
+ if (params.ttsModelId !== void 0) tts.model_id = resolveConvaiTtsModel(params.ttsModelId);
311
+ if (Object.keys(tts).length) conv.tts = tts;
312
+ if (Object.keys(conv).length) body.conversation_config = conv;
313
+ if (params.enableUserContextOverride) {
314
+ body.platform_settings = {
315
+ overrides: {
316
+ conversation_config_override: {
317
+ agent: { prompt: { prompt: true }, first_message: true, language: true },
318
+ tts: { voice_id: true },
319
+ conversation: { text_only: true }
320
+ },
321
+ custom_llm_extra_body: false,
322
+ enable_conversation_initiation_client_data_from_webhook: false
323
+ },
324
+ dynamic_variables: {
325
+ dynamic_variable_placeholders: {
326
+ user_context: "No prior memories yet.",
327
+ character_name: ""
328
+ }
329
+ }
330
+ };
331
+ }
332
+ if (!Object.keys(body).length) return;
333
+ const res = await this.fetchImpl(`${this.baseUrl}/v1/convai/agents/${encodeURIComponent(agentId)}`, {
334
+ method: "PATCH",
335
+ headers: this.headers({ "content-type": "application/json" }),
336
+ body: JSON.stringify(body)
337
+ });
338
+ if (!res.ok) {
339
+ const text = await res.text();
340
+ throw new ElevenLabsApiError(`patchAgent failed: ${res.status}`, res.status, text);
341
+ }
342
+ }
343
+ /**
344
+ * Delete a convai agent. Best-effort: swallow errors so teardown during a
345
+ * 500 response doesn't cascade into a second failure.
346
+ */
347
+ async deleteAgent(agentId) {
348
+ try {
349
+ await this.fetchImpl(`${this.baseUrl}/v1/convai/agents/${encodeURIComponent(agentId)}`, {
350
+ method: "DELETE",
351
+ headers: this.headers()
352
+ });
353
+ } catch {
354
+ }
355
+ }
356
+ /** Fetch a short-lived signed URL the browser can use to start a session. */
357
+ async getSignedUrl(agentId) {
358
+ const res = await this.fetchImpl(
359
+ `${this.baseUrl}/v1/convai/conversation/get-signed-url?agent_id=${encodeURIComponent(agentId)}`,
360
+ { headers: this.headers() }
361
+ );
362
+ if (!res.ok) {
363
+ const text = await res.text();
364
+ throw new ElevenLabsApiError(`getSignedUrl failed: ${res.status}`, res.status, text);
365
+ }
366
+ const data = await res.json();
367
+ return data.signed_url;
368
+ }
369
+ /**
370
+ * One-shot TTS. Returns the raw audio bytes plus the model id that was
371
+ * actually used (some models aren't available for every account, so we
372
+ * fall back to `eleven_turbo_v2_5` on the first failure).
373
+ */
374
+ async textToSpeech(params) {
375
+ const modelId = params.modelId && params.modelId.length > 0 ? params.modelId : "eleven_v3";
376
+ const accept = params.accept ?? "audio/mpeg";
377
+ const attempt = async (m) => {
378
+ const res2 = await this.fetchImpl(
379
+ `${this.baseUrl}/v1/text-to-speech/${encodeURIComponent(params.voiceId)}`,
380
+ {
381
+ method: "POST",
382
+ headers: this.headers({ "content-type": "application/json", accept }),
383
+ body: JSON.stringify({
384
+ text: params.text,
385
+ model_id: m,
386
+ voice_settings: params.voiceSettings
387
+ })
388
+ }
389
+ );
390
+ return res2;
391
+ };
392
+ let res = await attempt(modelId);
393
+ let used = modelId;
394
+ if (!res.ok && modelId !== "eleven_turbo_v2_5") {
395
+ res = await attempt("eleven_turbo_v2_5");
396
+ used = "eleven_turbo_v2_5";
397
+ }
398
+ if (!res.ok) {
399
+ const text = await res.text();
400
+ throw new ElevenLabsApiError(`textToSpeech failed: ${res.status}`, res.status, text);
401
+ }
402
+ const audio = await res.arrayBuffer();
403
+ const contentType = res.headers.get("content-type") ?? accept;
404
+ return { audio, modelId: used, contentType };
405
+ }
406
+ /** Probe whether a given voice id still exists on the caller's account. */
407
+ async voiceExists(voiceId) {
408
+ const res = await this.fetchImpl(
409
+ `${this.baseUrl}/v1/voices/${encodeURIComponent(voiceId)}`,
410
+ { headers: this.headers() }
411
+ );
412
+ return res.ok;
413
+ }
414
+ /**
415
+ * Register a Twilio phone number with an ElevenLabs agent. EL becomes
416
+ * the SIP target for inbound calls to that number — Twilio bridges
417
+ * the call via EL's native integration and we never have to run a
418
+ * Media Streams ↔ convai WebSocket bridge ourselves.
419
+ *
420
+ * Returns the ElevenLabs `phone_number_id` (distinct from the Twilio
421
+ * `sid`) which is the handle used by `outboundCall` + `deletePhoneNumber`.
422
+ */
423
+ async createPhoneNumberTwilio(params) {
424
+ const body = {
425
+ phone_number: params.phoneNumber,
426
+ label: params.label ?? params.phoneNumber,
427
+ provider: "twilio",
428
+ sid: params.twilioAccountSid,
429
+ token: params.twilioAuthToken
430
+ };
431
+ const res = await this.fetchImpl(
432
+ `${this.baseUrl}/v1/convai/phone-numbers`,
433
+ {
434
+ method: "POST",
435
+ headers: this.headers({ "content-type": "application/json" }),
436
+ body: JSON.stringify(body)
437
+ }
438
+ );
439
+ if (!res.ok) {
440
+ const text = await res.text();
441
+ throw new ElevenLabsApiError(
442
+ `createPhoneNumberTwilio failed: ${res.status}`,
443
+ res.status,
444
+ text
445
+ );
446
+ }
447
+ const data = await res.json();
448
+ const id = data.phone_number_id ?? data.id;
449
+ if (!id) {
450
+ throw new ElevenLabsApiError(
451
+ "createPhoneNumberTwilio returned no id",
452
+ 500,
453
+ JSON.stringify(data)
454
+ );
455
+ }
456
+ const patchRes = await this.fetchImpl(
457
+ `${this.baseUrl}/v1/convai/phone-numbers/${encodeURIComponent(id)}`,
458
+ {
459
+ method: "PATCH",
460
+ headers: this.headers({ "content-type": "application/json" }),
461
+ body: JSON.stringify({ agent_id: params.agentId })
462
+ }
463
+ );
464
+ if (!patchRes.ok) {
465
+ const text = await patchRes.text();
466
+ try {
467
+ await this.deletePhoneNumber(id);
468
+ } catch {
469
+ }
470
+ throw new ElevenLabsApiError(
471
+ `assignAgentToPhoneNumber failed: ${patchRes.status}`,
472
+ patchRes.status,
473
+ text
474
+ );
475
+ }
476
+ return { phoneNumberId: id };
477
+ }
478
+ /** Detach a previously-registered phone number from ElevenLabs. */
479
+ async deletePhoneNumber(phoneNumberId) {
480
+ const res = await this.fetchImpl(
481
+ `${this.baseUrl}/v1/convai/phone-numbers/${encodeURIComponent(phoneNumberId)}`,
482
+ { method: "DELETE", headers: this.headers() }
483
+ );
484
+ if (!res.ok && res.status !== 404) {
485
+ const text = await res.text();
486
+ throw new ElevenLabsApiError(
487
+ `deletePhoneNumber failed: ${res.status}`,
488
+ res.status,
489
+ text
490
+ );
491
+ }
492
+ }
493
+ /**
494
+ * Place an outbound PSTN call via a previously-registered EL phone
495
+ * number. EL drives Twilio (or a SIP trunk) under the hood — we just
496
+ * hand it the destination number and the agent to bridge to. Returns
497
+ * the Twilio `callSid` + EL `conversationId` for metering / tracing.
498
+ *
499
+ * As of 2026-Q2 the EL API uses provider-scoped routes:
500
+ * - Twilio numbers → POST /v1/convai/twilio/outbound-call
501
+ * - SIP numbers → POST /v1/convai/sip-trunk/outbound-call
502
+ * and `agent_phone_number_id` is a body field (no longer a path
503
+ * segment). The previous `/v1/convai/phone-numbers/{id}/outbound-call`
504
+ * route now returns 404. Callers that don't know the provider can
505
+ * look it up from `GET /v1/convai/phone-numbers/{id}` (`provider:
506
+ * "twilio" | "sip_trunk"`); most hosted setups are Twilio, so that's
507
+ * the default here.
508
+ */
509
+ async outboundCall(params) {
510
+ const provider = params.provider ?? "twilio";
511
+ const body = {
512
+ agent_id: params.agentId,
513
+ agent_phone_number_id: params.phoneNumberId,
514
+ to_number: params.toNumber
515
+ };
516
+ if (params.dynamicVariables) {
517
+ body.conversation_initiation_client_data = {
518
+ dynamic_variables: params.dynamicVariables
519
+ };
520
+ }
521
+ const res = await this.fetchImpl(
522
+ `${this.baseUrl}/v1/convai/${provider}/outbound-call`,
523
+ {
524
+ method: "POST",
525
+ headers: this.headers({ "content-type": "application/json" }),
526
+ body: JSON.stringify(body)
527
+ }
528
+ );
529
+ if (!res.ok) {
530
+ const text = await res.text();
531
+ throw new ElevenLabsApiError(
532
+ `outboundCall failed: ${res.status}`,
533
+ res.status,
534
+ text
535
+ );
536
+ }
537
+ const data = await res.json();
538
+ const callSid = data.call_sid ?? data.callSid;
539
+ const conversationId = data.conversation_id ?? data.conversationId;
540
+ if (!callSid || !conversationId) {
541
+ throw new ElevenLabsApiError(
542
+ "outboundCall missing callSid or conversationId",
543
+ 500,
544
+ JSON.stringify(data)
545
+ );
546
+ }
547
+ return { callSid, conversationId };
548
+ }
549
+ };
550
+ var MEMORY_CLIENT_TOOLS = [
551
+ {
552
+ type: "client",
553
+ name: "add_memory",
554
+ description: "Save a concise canonical fact about the user (preferences, decisions, personal details, follow-ups). Use whenever the user shares something worth remembering.",
555
+ expects_response: true,
556
+ parameters: {
557
+ type: "object",
558
+ properties: {
559
+ fact: {
560
+ type: "string",
561
+ description: 'A short normalized fact, e.g. "prefers window seats", "favorite color: green". Max 500 chars.'
562
+ }
563
+ },
564
+ required: ["fact"]
565
+ }
566
+ }
567
+ ];
568
+
569
+ // src/server.ts
570
+ function json(data, status = 200, extraHeaders = {}) {
571
+ return new Response(JSON.stringify(data), {
572
+ status,
573
+ headers: { "Content-Type": "application/json", ...extraHeaders }
574
+ });
575
+ }
576
+ async function readJson(req) {
577
+ try {
578
+ const text = await req.text();
579
+ if (!text) return {};
580
+ return JSON.parse(text);
581
+ } catch {
582
+ return null;
583
+ }
584
+ }
585
+ var NOOP_LOGGER = () => {
586
+ };
587
+ function resolveProxyOptions(config) {
588
+ if (config.proxy) {
589
+ if (!config.proxy.runtimeToken || !config.proxy.projectId || !config.proxy.apiUrl) {
590
+ throw new Error(
591
+ "createVoiceHandlers: proxy requires runtimeToken, projectId, and apiUrl"
592
+ );
593
+ }
594
+ return config.proxy;
595
+ }
596
+ if (typeof process === "undefined" || !process.env) return null;
597
+ const runtimeToken = process.env.RUNTIME_AUTH_SECRET;
598
+ const projectId = process.env.PROJECT_ID;
599
+ if (!runtimeToken || !projectId) return null;
600
+ const apiUrl = process.env.SHOGO_API_URL ?? process.env.SHOGO_CLOUD_URL ?? "http://localhost:8002";
601
+ return {
602
+ runtimeToken,
603
+ projectId,
604
+ apiUrl,
605
+ ...config.fetch ? { fetch: config.fetch } : {}
606
+ };
607
+ }
608
+ function createProxyHandlers(proxy, log) {
609
+ const fetchImpl = proxy.fetch ?? globalThis.fetch;
610
+ if (typeof fetchImpl !== "function") {
611
+ throw new Error(
612
+ "createVoiceHandlers proxy: global fetch is unavailable; pass proxy.fetch"
613
+ );
614
+ }
615
+ const apiBase = proxy.apiUrl.replace(/\/+$/, "");
616
+ function proxyUrl(path, forward) {
617
+ const tail = [`projectId=${encodeURIComponent(proxy.projectId)}`];
618
+ for (const [k, v] of Object.entries(forward)) {
619
+ if (typeof v === "string" && v.length > 0) {
620
+ tail.push(`${k}=${encodeURIComponent(v)}`);
621
+ }
622
+ }
623
+ const sep = path.includes("?") ? "&" : "?";
624
+ return `${apiBase}${path}${sep}${tail.join("&")}`;
625
+ }
626
+ const FORWARDED_SIGNED_URL_PARAMS = ["agentName"];
627
+ async function doSignedUrl(req) {
628
+ if (req.method !== "GET") return json({ error: "Method Not Allowed" }, 405);
629
+ try {
630
+ let incomingUrl = null;
631
+ try {
632
+ incomingUrl = new URL(req.url);
633
+ } catch {
634
+ }
635
+ const forward = {};
636
+ if (incomingUrl) {
637
+ for (const key of FORWARDED_SIGNED_URL_PARAMS) {
638
+ const v = incomingUrl.searchParams.get(key);
639
+ if (v != null) forward[key] = v;
640
+ }
641
+ }
642
+ const upstream = await fetchImpl(proxyUrl("/api/voice/signed-url", forward), {
643
+ method: "GET",
644
+ headers: { "x-runtime-token": proxy.runtimeToken },
645
+ credentials: "omit"
646
+ });
647
+ const body = await upstream.text();
648
+ const headers = { "content-type": "application/json" };
649
+ const ct = upstream.headers.get("content-type");
650
+ if (ct) headers["content-type"] = ct;
651
+ return new Response(body, { status: upstream.status, headers });
652
+ } catch (err) {
653
+ log("error", "proxy signedUrl failed", {
654
+ error: err instanceof Error ? err.message : String(err)
655
+ });
656
+ return json({ error: "signedUrl proxy failed" }, 502);
657
+ }
658
+ }
659
+ async function doAudioTags(req) {
660
+ if (req.method !== "GET") return json({ error: "Method Not Allowed" }, 405);
661
+ return json({
662
+ tags: AUDIO_TAGS,
663
+ groups: AUDIO_TAG_GROUPS,
664
+ expressivity: EXPRESSIVITY_OPTIONS,
665
+ defaults: {
666
+ allowedTags: DEFAULT_ALLOWED_TAGS,
667
+ voiceSettings: DEFAULT_VOICE_SETTINGS,
668
+ ttsModelId: CONVAI_TTS_MODEL_FALLBACK
669
+ }
670
+ });
671
+ }
672
+ function notImplemented(label) {
673
+ return async () => json(
674
+ {
675
+ error: "Not Implemented",
676
+ detail: `${label} is not available in runtime-token proxy mode; use BYO-EL or the hosted bearer SDK path.`
677
+ },
678
+ 501
679
+ );
680
+ }
681
+ return {
682
+ signedUrl: doSignedUrl,
683
+ tts: notImplemented("tts"),
684
+ agent: {
685
+ create: notImplemented("agent.create"),
686
+ patch: notImplemented("agent.patch"),
687
+ delete: notImplemented("agent.delete")
688
+ },
689
+ audioTags: doAudioTags,
690
+ // In proxy mode we don't own an EL client. Expose a throwing stub so
691
+ // accidental use is loud instead of silently passing around a
692
+ // half-configured client.
693
+ elevenLabs: new Proxy({}, {
694
+ get(_t, prop) {
695
+ throw new Error(
696
+ `createVoiceHandlers: elevenLabs.${String(prop)} is not available in runtime-token proxy mode`
697
+ );
698
+ }
699
+ })
700
+ };
701
+ }
702
+ function createVoiceHandlers(config) {
703
+ const log = config.logger ?? NOOP_LOGGER;
704
+ const proxy = resolveProxyOptions(config);
705
+ if (proxy) {
706
+ if (config.apiKey) {
707
+ log("warn", "createVoiceHandlers: apiKey provided but runtime-token proxy mode is active; apiKey is ignored");
708
+ }
709
+ return createProxyHandlers(proxy, log);
710
+ }
711
+ if (!config.apiKey) throw new Error("createVoiceHandlers: apiKey is required");
712
+ if (!config.getUser) throw new Error("createVoiceHandlers: getUser is required");
713
+ if (!config.companionStore) throw new Error("createVoiceHandlers: companionStore is required");
714
+ const getUser = config.getUser;
715
+ const companionStore = config.companionStore;
716
+ const includeMemoryTools = config.includeMemoryTools ?? true;
717
+ const tools = includeMemoryTools ? MEMORY_CLIENT_TOOLS : void 0;
718
+ const elConfig = {
719
+ apiKey: config.apiKey,
720
+ ...config.elevenLabsBaseUrl ? { baseUrl: config.elevenLabsBaseUrl } : {},
721
+ ...config.fetch ? { fetch: config.fetch } : {}
722
+ };
723
+ const el = new ElevenLabsClient(elConfig);
724
+ async function resolveUser(req) {
725
+ const u = await getUser(req);
726
+ if (!u) return json({ error: "Unauthorized" }, 401);
727
+ return u;
728
+ }
729
+ function handleElError(e, fallbackLabel) {
730
+ if (e instanceof ElevenLabsApiError) {
731
+ log("warn", `${fallbackLabel}: 11Labs ${e.status}`, { body: e.body });
732
+ return json({ error: fallbackLabel, detail: e.body }, 502);
733
+ }
734
+ const message = e instanceof Error ? e.message : String(e);
735
+ log("error", `${fallbackLabel}: ${message}`);
736
+ return json({ error: fallbackLabel, detail: message }, 500);
737
+ }
738
+ async function doSignedUrl(req) {
739
+ if (req.method !== "GET") return json({ error: "Method Not Allowed" }, 405);
740
+ const u = await resolveUser(req);
741
+ if (u instanceof Response) return u;
742
+ const companion = await companionStore.findByUserId(u.id);
743
+ if (!companion || !companion.agentId) return json({ error: "No companion" }, 404);
744
+ const basePrompt = extractBasePrompt(companion.systemPrompt || "");
745
+ try {
746
+ await el.patchAgent(companion.agentId, {
747
+ systemPrompt: basePrompt,
748
+ tools,
749
+ enableUserContextOverride: includeMemoryTools,
750
+ voiceId: companion.voiceId,
751
+ expressivity: readExpressivity(companion.expressivity),
752
+ audioTags: readAudioTags(companion.audioTags),
753
+ voiceSettings: readVoiceSettings(companion.voiceSettings),
754
+ ttsModelId: companion.ttsModelId ?? null,
755
+ ...includeMemoryTools ? {} : { memoryBlock: null }
756
+ });
757
+ if (basePrompt !== companion.systemPrompt) {
758
+ await companionStore.update(u.id, { systemPrompt: basePrompt });
759
+ }
760
+ } catch (e) {
761
+ log("warn", "signedUrl: pre-sync patch failed; continuing", {
762
+ error: e instanceof Error ? e.message : String(e)
763
+ });
764
+ }
765
+ let signedUrl;
766
+ try {
767
+ signedUrl = await el.getSignedUrl(companion.agentId);
768
+ } catch (e) {
769
+ return handleElError(e, "Failed to get signed url");
770
+ }
771
+ let userContext = "";
772
+ if (config.memoryStore) {
773
+ try {
774
+ const store = config.memoryStore(u.id);
775
+ const hits = store.search(companion.characterName || "recent context", { limit: 5 });
776
+ if (hits.length) {
777
+ userContext = hits.map(
778
+ (h) => `- ${h.matchType ? `[${h.matchType}] ` : ""}${h.chunk.trim().replace(/\s+/g, " ").slice(0, 220)}`
779
+ ).join("\n");
780
+ }
781
+ } catch (e) {
782
+ log("warn", "signedUrl: memory preload failed", {
783
+ error: e instanceof Error ? e.message : String(e)
784
+ });
785
+ }
786
+ }
787
+ return json({ signedUrl, agentId: companion.agentId, userContext });
788
+ }
789
+ async function doTts(req) {
790
+ if (req.method !== "POST") return json({ error: "Method Not Allowed" }, 405);
791
+ const u = await resolveUser(req);
792
+ if (u instanceof Response) return u;
793
+ const raw = await readJson(req);
794
+ if (raw === null || typeof raw !== "object") return json({ error: "Invalid JSON body" }, 400);
795
+ const body = raw;
796
+ const voiceId = typeof body.voiceId === "string" ? body.voiceId.trim() : "";
797
+ if (!voiceId) return json({ error: "Missing voiceId" }, 400);
798
+ const voiceSettings = normalizeVoiceSettings(body.voiceSettings) ?? { stability: 0.5, similarity_boost: 0.8, style: 0 };
799
+ let text;
800
+ if (typeof body.text === "string" && body.text.trim().length > 0) {
801
+ text = body.text.slice(0, 400);
802
+ } else {
803
+ const tags = normalizeAudioTags(body.audioTags) ?? [];
804
+ text = buildPreviewLine(tags, body.characterName ?? "your companion").slice(0, 400);
805
+ }
806
+ try {
807
+ const { audio, modelId, contentType } = await el.textToSpeech({
808
+ voiceId,
809
+ text,
810
+ ...body.modelId ? { modelId: body.modelId } : {},
811
+ voiceSettings
812
+ });
813
+ return new Response(audio, {
814
+ headers: {
815
+ "content-type": contentType,
816
+ "cache-control": "private, max-age=3600",
817
+ "x-tts-model-used": modelId
818
+ }
819
+ });
820
+ } catch (e) {
821
+ return handleElError(e, "TTS failed");
822
+ }
823
+ }
824
+ async function doAgentCreate(req) {
825
+ if (req.method !== "POST") return json({ error: "Method Not Allowed" }, 405);
826
+ const u = await resolveUser(req);
827
+ if (u instanceof Response) return u;
828
+ const existing = await companionStore.findByUserId(u.id);
829
+ if (existing) return json({ error: "Companion already exists" }, 409);
830
+ const raw = await readJson(req);
831
+ if (raw === null || typeof raw !== "object") return json({ error: "Invalid JSON body" }, 400);
832
+ const body = raw;
833
+ if (typeof body.displayName !== "string" || typeof body.characterName !== "string" || typeof body.voiceId !== "string" || typeof body.systemPrompt !== "string" || typeof body.firstMessage !== "string") {
834
+ return json({ error: "Missing required fields" }, 400);
835
+ }
836
+ const expressivity = readExpressivity(body.expressivity);
837
+ const audioTags = readAudioTags(body.audioTags);
838
+ const voiceSettings = readVoiceSettings(body.voiceSettings);
839
+ const ttsModelId = resolveConvaiTtsModel(body.ttsModelId);
840
+ let agentId;
841
+ try {
842
+ agentId = await el.createAgent({
843
+ displayName: body.displayName,
844
+ characterName: body.characterName,
845
+ voiceId: body.voiceId,
846
+ systemPrompt: body.systemPrompt,
847
+ firstMessage: body.firstMessage,
848
+ expressivity,
849
+ audioTags,
850
+ voiceSettings,
851
+ ttsModelId,
852
+ ...tools ? { tools } : {},
853
+ ...includeMemoryTools ? {} : { memoryBlock: null }
854
+ });
855
+ } catch (e) {
856
+ return handleElError(e, "Failed to create agent");
857
+ }
858
+ const cleanedPrompt = extractBasePrompt(body.systemPrompt);
859
+ const companion = await companionStore.create({
860
+ userId: u.id,
861
+ agentId,
862
+ displayName: body.displayName,
863
+ characterName: body.characterName,
864
+ voiceId: body.voiceId,
865
+ systemPrompt: cleanedPrompt,
866
+ firstMessage: body.firstMessage,
867
+ expressivity,
868
+ audioTags,
869
+ voiceSettings,
870
+ ttsModelId
871
+ });
872
+ return json(companion);
873
+ }
874
+ async function doAgentPatch(req) {
875
+ if (req.method !== "PATCH") return json({ error: "Method Not Allowed" }, 405);
876
+ const u = await resolveUser(req);
877
+ if (u instanceof Response) return u;
878
+ const existing = await companionStore.findByUserId(u.id);
879
+ if (!existing) return json({ error: "No companion" }, 404);
880
+ const raw = await readJson(req);
881
+ if (raw === null || typeof raw !== "object") return json({ error: "Invalid JSON body" }, 400);
882
+ const body = raw;
883
+ const patch = {};
884
+ if (body.displayName !== void 0) patch.displayName = body.displayName;
885
+ if (body.characterName !== void 0) patch.characterName = body.characterName;
886
+ if (body.voiceId !== void 0) patch.voiceId = body.voiceId;
887
+ if (body.firstMessage !== void 0) patch.firstMessage = body.firstMessage;
888
+ const expressivity = normalizeExpressivity(body.expressivity);
889
+ if (expressivity !== void 0) patch.expressivity = expressivity;
890
+ let audioTagsPatch;
891
+ if (body.audioTags !== void 0) {
892
+ audioTagsPatch = normalizeAudioTags(body.audioTags) ?? [];
893
+ patch.audioTags = audioTagsPatch;
894
+ }
895
+ if (body.voiceSettings !== void 0) {
896
+ const vs = normalizeVoiceSettings(body.voiceSettings);
897
+ patch.voiceSettings = vs ?? null;
898
+ }
899
+ if (body.ttsModelId !== void 0) patch.ttsModelId = body.ttsModelId ?? null;
900
+ const cleanedPrompt = body.systemPrompt !== void 0 ? extractBasePrompt(body.systemPrompt) : void 0;
901
+ if (cleanedPrompt !== void 0) patch.systemPrompt = cleanedPrompt;
902
+ const needs11Labs = body.displayName !== void 0 || body.characterName !== void 0 || body.voiceId !== void 0 || body.systemPrompt !== void 0 || body.firstMessage !== void 0 || body.expressivity !== void 0 || body.audioTags !== void 0 || body.voiceSettings !== void 0 || body.ttsModelId !== void 0;
903
+ if (existing.agentId && needs11Labs) {
904
+ const finalExpressivity = patch.expressivity ?? readExpressivity(existing.expressivity);
905
+ const finalTags = audioTagsPatch !== void 0 ? audioTagsPatch : readAudioTags(existing.audioTags);
906
+ const finalVoiceSettings = body.voiceSettings !== void 0 ? normalizeVoiceSettings(body.voiceSettings) ?? void 0 : readVoiceSettings(existing.voiceSettings);
907
+ const finalTtsModelId = body.ttsModelId !== void 0 ? body.ttsModelId ?? null : existing.ttsModelId ?? null;
908
+ try {
909
+ await el.patchAgent(existing.agentId, {
910
+ ...body.displayName !== void 0 ? { displayName: body.displayName } : {},
911
+ ...body.characterName !== void 0 ? { characterName: body.characterName } : {},
912
+ ...body.voiceId !== void 0 ? { voiceId: body.voiceId } : {},
913
+ systemPrompt: cleanedPrompt !== void 0 ? cleanedPrompt : body.expressivity !== void 0 || body.audioTags !== void 0 ? existing.systemPrompt : void 0,
914
+ ...body.firstMessage !== void 0 ? { firstMessage: body.firstMessage } : {},
915
+ expressivity: finalExpressivity,
916
+ audioTags: finalTags,
917
+ ...finalVoiceSettings ? { voiceSettings: finalVoiceSettings } : {},
918
+ ttsModelId: finalTtsModelId,
919
+ ...includeMemoryTools ? {} : { memoryBlock: null }
920
+ });
921
+ } catch (e) {
922
+ return handleElError(e, "Failed to patch agent");
923
+ }
924
+ }
925
+ const companion = await companionStore.update(u.id, patch);
926
+ return json(companion);
927
+ }
928
+ async function doAgentDelete(req) {
929
+ if (req.method !== "DELETE") return json({ error: "Method Not Allowed" }, 405);
930
+ const u = await resolveUser(req);
931
+ if (u instanceof Response) return u;
932
+ const existing = await companionStore.findByUserId(u.id);
933
+ if (!existing) return json({ ok: true });
934
+ if (existing.agentId) await el.deleteAgent(existing.agentId);
935
+ await companionStore.delete(u.id);
936
+ return json({ ok: true });
937
+ }
938
+ async function doAudioTags(req) {
939
+ if (req.method !== "GET") return json({ error: "Method Not Allowed" }, 405);
940
+ return json({
941
+ tags: AUDIO_TAGS,
942
+ groups: AUDIO_TAG_GROUPS,
943
+ expressivity: EXPRESSIVITY_OPTIONS,
944
+ defaults: {
945
+ allowedTags: DEFAULT_ALLOWED_TAGS,
946
+ voiceSettings: DEFAULT_VOICE_SETTINGS,
947
+ ttsModelId: CONVAI_TTS_MODEL_FALLBACK
948
+ }
949
+ });
950
+ }
951
+ return {
952
+ signedUrl: doSignedUrl,
953
+ tts: doTts,
954
+ agent: {
955
+ create: doAgentCreate,
956
+ patch: doAgentPatch,
957
+ delete: doAgentDelete
958
+ },
959
+ audioTags: doAudioTags,
960
+ elevenLabs: el
961
+ };
962
+ }
963
+
964
+ // src/route/index.ts
965
+ function defaultHandlers() {
966
+ return createVoiceHandlers({});
967
+ }
968
+
969
+ // src/route/audio-tags.ts
970
+ var GET = (req) => defaultHandlers().audioTags(req);
971
+ // Annotate the CommonJS export names for ESM import in node:
972
+ 0 && (module.exports = {
973
+ GET
974
+ });
975
+ //# sourceMappingURL=audio-tags.cjs.map