@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,423 @@
1
+ import {
2
+ composeExpressivityBlock,
3
+ stripExpressivityBlock
4
+ } from "./chunk-Q3TJAJ54.js";
5
+
6
+ // src/prompt.ts
7
+ var DEFAULT_MEMORY_BLOCK = `# Memory
8
+
9
+ Known context about this user from past conversations:
10
+ {{user_context}}
11
+
12
+ Memory retrieval is automatic \u2014 the top matching facts are already injected above. Never say "let me check my memory" or "searching memories."
13
+
14
+ You have one tool:
15
+ - 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.`;
16
+ function stripMemoryBlock(prompt) {
17
+ if (!prompt) return prompt;
18
+ 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();
19
+ }
20
+ function extractBasePrompt(prompt) {
21
+ return stripExpressivityBlock(stripMemoryBlock(prompt));
22
+ }
23
+ function composeAgentPrompt(basePrompt, options = {}) {
24
+ const { expressivity = "subtle", audioTags = null, memoryBlock = DEFAULT_MEMORY_BLOCK } = options;
25
+ const stripped = extractBasePrompt(basePrompt);
26
+ const expr = composeExpressivityBlock(expressivity, audioTags);
27
+ const parts = [stripped];
28
+ if (expr) parts.push(expr);
29
+ if (memoryBlock) parts.push(memoryBlock);
30
+ return parts.join("\n\n");
31
+ }
32
+
33
+ // src/elevenlabs.ts
34
+ var DEFAULT_ELEVENLABS_BASE_URL = "https://api.elevenlabs.io";
35
+ var CONVAI_SUPPORTED_TTS_MODELS = /* @__PURE__ */ new Set([
36
+ "eleven_turbo_v2",
37
+ "eleven_turbo_v2_5",
38
+ "eleven_flash_v2",
39
+ "eleven_flash_v2_5",
40
+ "eleven_multilingual_v2"
41
+ ]);
42
+ var CONVAI_TTS_MODEL_FALLBACK = "eleven_turbo_v2_5";
43
+ function resolveConvaiTtsModel(requested) {
44
+ if (requested && CONVAI_SUPPORTED_TTS_MODELS.has(requested)) return requested;
45
+ return CONVAI_TTS_MODEL_FALLBACK;
46
+ }
47
+ var ElevenLabsApiError = class extends Error {
48
+ constructor(message, status, body) {
49
+ super(message);
50
+ this.status = status;
51
+ this.body = body;
52
+ this.name = "ElevenLabsApiError";
53
+ }
54
+ };
55
+ var ElevenLabsClient = class {
56
+ constructor(config) {
57
+ this.config = config;
58
+ if (!config.apiKey) throw new Error("ElevenLabsClient: apiKey is required");
59
+ this.baseUrl = (config.baseUrl ?? DEFAULT_ELEVENLABS_BASE_URL).replace(/\/+$/, "");
60
+ this.fetchImpl = config.fetch ?? globalThis.fetch;
61
+ if (typeof this.fetchImpl !== "function") {
62
+ throw new Error("ElevenLabsClient: global fetch is unavailable; pass config.fetch");
63
+ }
64
+ }
65
+ headers(extra = {}) {
66
+ return {
67
+ "xi-api-key": this.config.apiKey,
68
+ ...extra
69
+ };
70
+ }
71
+ /** Create a new convai agent. Returns the new `agent_id`. */
72
+ async createAgent(params) {
73
+ const prompt = composeAgentPrompt(params.systemPrompt, {
74
+ expressivity: params.expressivity ?? "subtle",
75
+ audioTags: params.audioTags ?? null,
76
+ memoryBlock: params.memoryBlock
77
+ });
78
+ const ttsConfig = {
79
+ voice_id: params.voiceId,
80
+ model_id: resolveConvaiTtsModel(params.ttsModelId)
81
+ };
82
+ if (params.voiceSettings) ttsConfig.voice_settings = params.voiceSettings;
83
+ const body = {
84
+ name: `Companion-${params.characterName}-for-${params.displayName}`,
85
+ conversation_config: {
86
+ agent: {
87
+ prompt: {
88
+ prompt,
89
+ ...params.tools ? { tools: params.tools } : {}
90
+ },
91
+ first_message: params.firstMessage,
92
+ language: params.language ?? "en"
93
+ },
94
+ tts: ttsConfig
95
+ },
96
+ platform_settings: {
97
+ overrides: {
98
+ conversation_config_override: {
99
+ agent: { prompt: { prompt: true }, first_message: true, language: true },
100
+ tts: { voice_id: true },
101
+ conversation: { text_only: true }
102
+ }
103
+ },
104
+ dynamic_variables: {
105
+ dynamic_variable_placeholders: {
106
+ user_context: "No prior memories yet.",
107
+ character_name: "",
108
+ ...params.dynamicVariablePlaceholders ?? {}
109
+ }
110
+ }
111
+ }
112
+ };
113
+ const res = await this.fetchImpl(`${this.baseUrl}/v1/convai/agents/create`, {
114
+ method: "POST",
115
+ headers: this.headers({ "content-type": "application/json" }),
116
+ body: JSON.stringify(body)
117
+ });
118
+ if (!res.ok) {
119
+ const text = await res.text();
120
+ throw new ElevenLabsApiError(`createAgent failed: ${res.status}`, res.status, text);
121
+ }
122
+ const data = await res.json();
123
+ return data.agent_id;
124
+ }
125
+ /** PATCH any subset of agent settings. Idempotent; only sends the keys you supply. */
126
+ async patchAgent(agentId, params) {
127
+ const body = {};
128
+ if (params.displayName !== void 0 || params.characterName !== void 0) {
129
+ body.name = `Companion-${params.characterName ?? ""}-for-${params.displayName ?? ""}`.replace(
130
+ /-for-$/,
131
+ ""
132
+ );
133
+ }
134
+ const conv = {};
135
+ const agent = {};
136
+ const prompt = {};
137
+ if (params.systemPrompt !== void 0) {
138
+ prompt.prompt = composeAgentPrompt(params.systemPrompt, {
139
+ expressivity: params.expressivity ?? "subtle",
140
+ audioTags: params.audioTags ?? null,
141
+ memoryBlock: params.memoryBlock
142
+ });
143
+ }
144
+ if (params.tools) prompt.tools = params.tools;
145
+ if (Object.keys(prompt).length) agent.prompt = prompt;
146
+ if (params.firstMessage !== void 0) agent.first_message = params.firstMessage;
147
+ if (Object.keys(agent).length) conv.agent = agent;
148
+ const tts = {};
149
+ if (params.voiceId !== void 0) tts.voice_id = params.voiceId;
150
+ if (params.voiceSettings) tts.voice_settings = params.voiceSettings;
151
+ if (params.ttsModelId !== void 0) tts.model_id = resolveConvaiTtsModel(params.ttsModelId);
152
+ if (Object.keys(tts).length) conv.tts = tts;
153
+ if (Object.keys(conv).length) body.conversation_config = conv;
154
+ if (params.enableUserContextOverride) {
155
+ body.platform_settings = {
156
+ overrides: {
157
+ conversation_config_override: {
158
+ agent: { prompt: { prompt: true }, first_message: true, language: true },
159
+ tts: { voice_id: true },
160
+ conversation: { text_only: true }
161
+ },
162
+ custom_llm_extra_body: false,
163
+ enable_conversation_initiation_client_data_from_webhook: false
164
+ },
165
+ dynamic_variables: {
166
+ dynamic_variable_placeholders: {
167
+ user_context: "No prior memories yet.",
168
+ character_name: ""
169
+ }
170
+ }
171
+ };
172
+ }
173
+ if (!Object.keys(body).length) return;
174
+ const res = await this.fetchImpl(`${this.baseUrl}/v1/convai/agents/${encodeURIComponent(agentId)}`, {
175
+ method: "PATCH",
176
+ headers: this.headers({ "content-type": "application/json" }),
177
+ body: JSON.stringify(body)
178
+ });
179
+ if (!res.ok) {
180
+ const text = await res.text();
181
+ throw new ElevenLabsApiError(`patchAgent failed: ${res.status}`, res.status, text);
182
+ }
183
+ }
184
+ /**
185
+ * Delete a convai agent. Best-effort: swallow errors so teardown during a
186
+ * 500 response doesn't cascade into a second failure.
187
+ */
188
+ async deleteAgent(agentId) {
189
+ try {
190
+ await this.fetchImpl(`${this.baseUrl}/v1/convai/agents/${encodeURIComponent(agentId)}`, {
191
+ method: "DELETE",
192
+ headers: this.headers()
193
+ });
194
+ } catch {
195
+ }
196
+ }
197
+ /** Fetch a short-lived signed URL the browser can use to start a session. */
198
+ async getSignedUrl(agentId) {
199
+ const res = await this.fetchImpl(
200
+ `${this.baseUrl}/v1/convai/conversation/get-signed-url?agent_id=${encodeURIComponent(agentId)}`,
201
+ { headers: this.headers() }
202
+ );
203
+ if (!res.ok) {
204
+ const text = await res.text();
205
+ throw new ElevenLabsApiError(`getSignedUrl failed: ${res.status}`, res.status, text);
206
+ }
207
+ const data = await res.json();
208
+ return data.signed_url;
209
+ }
210
+ /**
211
+ * One-shot TTS. Returns the raw audio bytes plus the model id that was
212
+ * actually used (some models aren't available for every account, so we
213
+ * fall back to `eleven_turbo_v2_5` on the first failure).
214
+ */
215
+ async textToSpeech(params) {
216
+ const modelId = params.modelId && params.modelId.length > 0 ? params.modelId : "eleven_v3";
217
+ const accept = params.accept ?? "audio/mpeg";
218
+ const attempt = async (m) => {
219
+ const res2 = await this.fetchImpl(
220
+ `${this.baseUrl}/v1/text-to-speech/${encodeURIComponent(params.voiceId)}`,
221
+ {
222
+ method: "POST",
223
+ headers: this.headers({ "content-type": "application/json", accept }),
224
+ body: JSON.stringify({
225
+ text: params.text,
226
+ model_id: m,
227
+ voice_settings: params.voiceSettings
228
+ })
229
+ }
230
+ );
231
+ return res2;
232
+ };
233
+ let res = await attempt(modelId);
234
+ let used = modelId;
235
+ if (!res.ok && modelId !== "eleven_turbo_v2_5") {
236
+ res = await attempt("eleven_turbo_v2_5");
237
+ used = "eleven_turbo_v2_5";
238
+ }
239
+ if (!res.ok) {
240
+ const text = await res.text();
241
+ throw new ElevenLabsApiError(`textToSpeech failed: ${res.status}`, res.status, text);
242
+ }
243
+ const audio = await res.arrayBuffer();
244
+ const contentType = res.headers.get("content-type") ?? accept;
245
+ return { audio, modelId: used, contentType };
246
+ }
247
+ /** Probe whether a given voice id still exists on the caller's account. */
248
+ async voiceExists(voiceId) {
249
+ const res = await this.fetchImpl(
250
+ `${this.baseUrl}/v1/voices/${encodeURIComponent(voiceId)}`,
251
+ { headers: this.headers() }
252
+ );
253
+ return res.ok;
254
+ }
255
+ /**
256
+ * Register a Twilio phone number with an ElevenLabs agent. EL becomes
257
+ * the SIP target for inbound calls to that number — Twilio bridges
258
+ * the call via EL's native integration and we never have to run a
259
+ * Media Streams ↔ convai WebSocket bridge ourselves.
260
+ *
261
+ * Returns the ElevenLabs `phone_number_id` (distinct from the Twilio
262
+ * `sid`) which is the handle used by `outboundCall` + `deletePhoneNumber`.
263
+ */
264
+ async createPhoneNumberTwilio(params) {
265
+ const body = {
266
+ phone_number: params.phoneNumber,
267
+ label: params.label ?? params.phoneNumber,
268
+ provider: "twilio",
269
+ sid: params.twilioAccountSid,
270
+ token: params.twilioAuthToken
271
+ };
272
+ const res = await this.fetchImpl(
273
+ `${this.baseUrl}/v1/convai/phone-numbers`,
274
+ {
275
+ method: "POST",
276
+ headers: this.headers({ "content-type": "application/json" }),
277
+ body: JSON.stringify(body)
278
+ }
279
+ );
280
+ if (!res.ok) {
281
+ const text = await res.text();
282
+ throw new ElevenLabsApiError(
283
+ `createPhoneNumberTwilio failed: ${res.status}`,
284
+ res.status,
285
+ text
286
+ );
287
+ }
288
+ const data = await res.json();
289
+ const id = data.phone_number_id ?? data.id;
290
+ if (!id) {
291
+ throw new ElevenLabsApiError(
292
+ "createPhoneNumberTwilio returned no id",
293
+ 500,
294
+ JSON.stringify(data)
295
+ );
296
+ }
297
+ const patchRes = await this.fetchImpl(
298
+ `${this.baseUrl}/v1/convai/phone-numbers/${encodeURIComponent(id)}`,
299
+ {
300
+ method: "PATCH",
301
+ headers: this.headers({ "content-type": "application/json" }),
302
+ body: JSON.stringify({ agent_id: params.agentId })
303
+ }
304
+ );
305
+ if (!patchRes.ok) {
306
+ const text = await patchRes.text();
307
+ try {
308
+ await this.deletePhoneNumber(id);
309
+ } catch {
310
+ }
311
+ throw new ElevenLabsApiError(
312
+ `assignAgentToPhoneNumber failed: ${patchRes.status}`,
313
+ patchRes.status,
314
+ text
315
+ );
316
+ }
317
+ return { phoneNumberId: id };
318
+ }
319
+ /** Detach a previously-registered phone number from ElevenLabs. */
320
+ async deletePhoneNumber(phoneNumberId) {
321
+ const res = await this.fetchImpl(
322
+ `${this.baseUrl}/v1/convai/phone-numbers/${encodeURIComponent(phoneNumberId)}`,
323
+ { method: "DELETE", headers: this.headers() }
324
+ );
325
+ if (!res.ok && res.status !== 404) {
326
+ const text = await res.text();
327
+ throw new ElevenLabsApiError(
328
+ `deletePhoneNumber failed: ${res.status}`,
329
+ res.status,
330
+ text
331
+ );
332
+ }
333
+ }
334
+ /**
335
+ * Place an outbound PSTN call via a previously-registered EL phone
336
+ * number. EL drives Twilio (or a SIP trunk) under the hood — we just
337
+ * hand it the destination number and the agent to bridge to. Returns
338
+ * the Twilio `callSid` + EL `conversationId` for metering / tracing.
339
+ *
340
+ * As of 2026-Q2 the EL API uses provider-scoped routes:
341
+ * - Twilio numbers → POST /v1/convai/twilio/outbound-call
342
+ * - SIP numbers → POST /v1/convai/sip-trunk/outbound-call
343
+ * and `agent_phone_number_id` is a body field (no longer a path
344
+ * segment). The previous `/v1/convai/phone-numbers/{id}/outbound-call`
345
+ * route now returns 404. Callers that don't know the provider can
346
+ * look it up from `GET /v1/convai/phone-numbers/{id}` (`provider:
347
+ * "twilio" | "sip_trunk"`); most hosted setups are Twilio, so that's
348
+ * the default here.
349
+ */
350
+ async outboundCall(params) {
351
+ const provider = params.provider ?? "twilio";
352
+ const body = {
353
+ agent_id: params.agentId,
354
+ agent_phone_number_id: params.phoneNumberId,
355
+ to_number: params.toNumber
356
+ };
357
+ if (params.dynamicVariables) {
358
+ body.conversation_initiation_client_data = {
359
+ dynamic_variables: params.dynamicVariables
360
+ };
361
+ }
362
+ const res = await this.fetchImpl(
363
+ `${this.baseUrl}/v1/convai/${provider}/outbound-call`,
364
+ {
365
+ method: "POST",
366
+ headers: this.headers({ "content-type": "application/json" }),
367
+ body: JSON.stringify(body)
368
+ }
369
+ );
370
+ if (!res.ok) {
371
+ const text = await res.text();
372
+ throw new ElevenLabsApiError(
373
+ `outboundCall failed: ${res.status}`,
374
+ res.status,
375
+ text
376
+ );
377
+ }
378
+ const data = await res.json();
379
+ const callSid = data.call_sid ?? data.callSid;
380
+ const conversationId = data.conversation_id ?? data.conversationId;
381
+ if (!callSid || !conversationId) {
382
+ throw new ElevenLabsApiError(
383
+ "outboundCall missing callSid or conversationId",
384
+ 500,
385
+ JSON.stringify(data)
386
+ );
387
+ }
388
+ return { callSid, conversationId };
389
+ }
390
+ };
391
+ var MEMORY_CLIENT_TOOLS = [
392
+ {
393
+ type: "client",
394
+ name: "add_memory",
395
+ description: "Save a concise canonical fact about the user (preferences, decisions, personal details, follow-ups). Use whenever the user shares something worth remembering.",
396
+ expects_response: true,
397
+ parameters: {
398
+ type: "object",
399
+ properties: {
400
+ fact: {
401
+ type: "string",
402
+ description: 'A short normalized fact, e.g. "prefers window seats", "favorite color: green". Max 500 chars.'
403
+ }
404
+ },
405
+ required: ["fact"]
406
+ }
407
+ }
408
+ ];
409
+
410
+ export {
411
+ DEFAULT_MEMORY_BLOCK,
412
+ stripMemoryBlock,
413
+ extractBasePrompt,
414
+ composeAgentPrompt,
415
+ DEFAULT_ELEVENLABS_BASE_URL,
416
+ CONVAI_SUPPORTED_TTS_MODELS,
417
+ CONVAI_TTS_MODEL_FALLBACK,
418
+ resolveConvaiTtsModel,
419
+ ElevenLabsApiError,
420
+ ElevenLabsClient,
421
+ MEMORY_CLIENT_TOOLS
422
+ };
423
+ //# sourceMappingURL=chunk-ZGYHDC5G.js.map