@openclaw/voice-call 2026.5.2 → 2026.5.3-beta.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 (126) hide show
  1. package/dist/api.js +2 -0
  2. package/dist/call-status-CXldV5o8.js +32 -0
  3. package/dist/cli-metadata.js +12 -0
  4. package/dist/config-7w04YpHh.js +548 -0
  5. package/dist/config-compat-B0me39_4.js +129 -0
  6. package/dist/guarded-json-api-Btx5EE4w.js +591 -0
  7. package/dist/http-headers-BrnxBasF.js +10 -0
  8. package/dist/index.js +1284 -0
  9. package/dist/mock-CeKvfVEd.js +135 -0
  10. package/dist/plivo-B-a7KFoT.js +393 -0
  11. package/dist/realtime-handler-B63CIDP2.js +325 -0
  12. package/dist/realtime-transcription.runtime-B2h70y2W.js +2 -0
  13. package/dist/realtime-voice.runtime-Bkh4nvLn.js +2 -0
  14. package/dist/response-generator-BrcmwDZU.js +182 -0
  15. package/dist/response-model-CyF5K80p.js +12 -0
  16. package/dist/runtime-api.js +6 -0
  17. package/dist/runtime-entry-88ytYAQa.js +3119 -0
  18. package/dist/runtime-entry.js +2 -0
  19. package/dist/setup-api.js +37 -0
  20. package/dist/telnyx-jjBE8boz.js +260 -0
  21. package/dist/twilio-1OqbcXLL.js +676 -0
  22. package/dist/voice-mapping-BYDGdWGx.js +40 -0
  23. package/package.json +14 -6
  24. package/api.ts +0 -16
  25. package/cli-metadata.ts +0 -10
  26. package/config-api.ts +0 -12
  27. package/index.test.ts +0 -943
  28. package/index.ts +0 -794
  29. package/runtime-api.ts +0 -20
  30. package/runtime-entry.ts +0 -1
  31. package/setup-api.ts +0 -47
  32. package/src/allowlist.test.ts +0 -18
  33. package/src/allowlist.ts +0 -19
  34. package/src/cli.ts +0 -845
  35. package/src/config-compat.test.ts +0 -120
  36. package/src/config-compat.ts +0 -227
  37. package/src/config.test.ts +0 -479
  38. package/src/config.ts +0 -808
  39. package/src/core-bridge.ts +0 -14
  40. package/src/deep-merge.test.ts +0 -40
  41. package/src/deep-merge.ts +0 -23
  42. package/src/gateway-continue-operation.ts +0 -200
  43. package/src/http-headers.test.ts +0 -16
  44. package/src/http-headers.ts +0 -15
  45. package/src/manager/context.ts +0 -42
  46. package/src/manager/events.test.ts +0 -581
  47. package/src/manager/events.ts +0 -288
  48. package/src/manager/lifecycle.ts +0 -53
  49. package/src/manager/lookup.test.ts +0 -52
  50. package/src/manager/lookup.ts +0 -35
  51. package/src/manager/outbound.test.ts +0 -528
  52. package/src/manager/outbound.ts +0 -486
  53. package/src/manager/state.ts +0 -48
  54. package/src/manager/store.ts +0 -106
  55. package/src/manager/timers.test.ts +0 -129
  56. package/src/manager/timers.ts +0 -113
  57. package/src/manager/twiml.test.ts +0 -13
  58. package/src/manager/twiml.ts +0 -17
  59. package/src/manager.closed-loop.test.ts +0 -236
  60. package/src/manager.inbound-allowlist.test.ts +0 -188
  61. package/src/manager.notify.test.ts +0 -377
  62. package/src/manager.restore.test.ts +0 -183
  63. package/src/manager.test-harness.ts +0 -127
  64. package/src/manager.ts +0 -392
  65. package/src/media-stream.test.ts +0 -768
  66. package/src/media-stream.ts +0 -708
  67. package/src/providers/base.ts +0 -97
  68. package/src/providers/mock.test.ts +0 -78
  69. package/src/providers/mock.ts +0 -185
  70. package/src/providers/plivo.test.ts +0 -93
  71. package/src/providers/plivo.ts +0 -601
  72. package/src/providers/shared/call-status.test.ts +0 -24
  73. package/src/providers/shared/call-status.ts +0 -24
  74. package/src/providers/shared/guarded-json-api.test.ts +0 -106
  75. package/src/providers/shared/guarded-json-api.ts +0 -42
  76. package/src/providers/telnyx.test.ts +0 -340
  77. package/src/providers/telnyx.ts +0 -394
  78. package/src/providers/twilio/api.test.ts +0 -145
  79. package/src/providers/twilio/api.ts +0 -93
  80. package/src/providers/twilio/twiml-policy.test.ts +0 -84
  81. package/src/providers/twilio/twiml-policy.ts +0 -87
  82. package/src/providers/twilio/webhook.ts +0 -34
  83. package/src/providers/twilio.test.ts +0 -591
  84. package/src/providers/twilio.ts +0 -861
  85. package/src/providers/twilio.types.ts +0 -17
  86. package/src/realtime-defaults.ts +0 -3
  87. package/src/realtime-fast-context.test.ts +0 -88
  88. package/src/realtime-fast-context.ts +0 -165
  89. package/src/realtime-transcription.runtime.ts +0 -4
  90. package/src/realtime-voice.runtime.ts +0 -5
  91. package/src/response-generator.test.ts +0 -321
  92. package/src/response-generator.ts +0 -318
  93. package/src/response-model.test.ts +0 -71
  94. package/src/response-model.ts +0 -23
  95. package/src/runtime.test.ts +0 -536
  96. package/src/runtime.ts +0 -510
  97. package/src/telephony-audio.test.ts +0 -61
  98. package/src/telephony-audio.ts +0 -12
  99. package/src/telephony-tts.test.ts +0 -196
  100. package/src/telephony-tts.ts +0 -235
  101. package/src/test-fixtures.ts +0 -73
  102. package/src/tts-provider-voice.test.ts +0 -34
  103. package/src/tts-provider-voice.ts +0 -21
  104. package/src/tunnel.test.ts +0 -166
  105. package/src/tunnel.ts +0 -314
  106. package/src/types.ts +0 -291
  107. package/src/utils.test.ts +0 -17
  108. package/src/utils.ts +0 -14
  109. package/src/voice-mapping.test.ts +0 -34
  110. package/src/voice-mapping.ts +0 -68
  111. package/src/webhook/realtime-handler.test.ts +0 -598
  112. package/src/webhook/realtime-handler.ts +0 -485
  113. package/src/webhook/stale-call-reaper.test.ts +0 -88
  114. package/src/webhook/stale-call-reaper.ts +0 -38
  115. package/src/webhook/tailscale.test.ts +0 -214
  116. package/src/webhook/tailscale.ts +0 -129
  117. package/src/webhook-exposure.test.ts +0 -33
  118. package/src/webhook-exposure.ts +0 -84
  119. package/src/webhook-security.test.ts +0 -770
  120. package/src/webhook-security.ts +0 -994
  121. package/src/webhook.hangup-once.lifecycle.test.ts +0 -135
  122. package/src/webhook.test.ts +0 -1470
  123. package/src/webhook.ts +0 -908
  124. package/src/webhook.types.ts +0 -5
  125. package/src/websocket-test-support.ts +0 -72
  126. package/tsconfig.json +0 -16
@@ -1,479 +0,0 @@
1
- import { afterEach, beforeEach, describe, expect, it } from "vitest";
2
- import {
3
- VoiceCallConfigSchema,
4
- resolveTwilioAuthToken,
5
- resolveVoiceCallEffectiveConfig,
6
- resolveVoiceCallNumberRouteKey,
7
- resolveVoiceCallSessionKey,
8
- validateProviderConfig,
9
- normalizeVoiceCallConfig,
10
- resolveVoiceCallConfig,
11
- type VoiceCallConfig,
12
- } from "./config.js";
13
- import { createVoiceCallBaseConfig } from "./test-fixtures.js";
14
-
15
- function createBaseConfig(provider: "telnyx" | "twilio" | "plivo" | "mock"): VoiceCallConfig {
16
- return createVoiceCallBaseConfig({ provider });
17
- }
18
-
19
- function envRef(id: string) {
20
- return { source: "env" as const, provider: "default", id };
21
- }
22
-
23
- function requireElevenLabsTtsConfig(config: Pick<VoiceCallConfig, "tts">) {
24
- const tts = config.tts;
25
- const elevenlabs = tts?.providers?.elevenlabs;
26
- if (!elevenlabs || typeof elevenlabs !== "object") {
27
- throw new Error("voice-call config did not preserve nested elevenlabs TTS config");
28
- }
29
- return { tts, elevenlabs };
30
- }
31
-
32
- describe("validateProviderConfig", () => {
33
- const originalEnv = { ...process.env };
34
- const clearProviderEnv = () => {
35
- delete process.env.TWILIO_ACCOUNT_SID;
36
- delete process.env.TWILIO_AUTH_TOKEN;
37
- delete process.env.TWILIO_FROM_NUMBER;
38
- delete process.env.TELNYX_API_KEY;
39
- delete process.env.TELNYX_CONNECTION_ID;
40
- delete process.env.TELNYX_PUBLIC_KEY;
41
- delete process.env.PLIVO_AUTH_ID;
42
- delete process.env.PLIVO_AUTH_TOKEN;
43
- };
44
-
45
- beforeEach(() => {
46
- clearProviderEnv();
47
- });
48
-
49
- afterEach(() => {
50
- // Restore original env
51
- process.env = { ...originalEnv };
52
- });
53
-
54
- describe("provider credential sources", () => {
55
- it("passes validation when credentials come from config or environment", () => {
56
- for (const provider of ["twilio", "telnyx", "plivo"] as const) {
57
- clearProviderEnv();
58
- const fromConfig = createBaseConfig(provider);
59
- if (provider === "twilio") {
60
- fromConfig.twilio = { accountSid: "AC123", authToken: "secret" };
61
- } else if (provider === "telnyx") {
62
- fromConfig.telnyx = {
63
- apiKey: "KEY123",
64
- connectionId: "CONN456",
65
- publicKey: "public-key",
66
- };
67
- } else {
68
- fromConfig.plivo = { authId: "MA123", authToken: "secret" };
69
- }
70
- expect(validateProviderConfig(fromConfig)).toMatchObject({ valid: true, errors: [] });
71
-
72
- clearProviderEnv();
73
- if (provider === "twilio") {
74
- process.env.TWILIO_ACCOUNT_SID = "AC123";
75
- process.env.TWILIO_AUTH_TOKEN = "secret";
76
- process.env.TWILIO_FROM_NUMBER = "+15550001234";
77
- } else if (provider === "telnyx") {
78
- process.env.TELNYX_API_KEY = "KEY123";
79
- process.env.TELNYX_CONNECTION_ID = "CONN456";
80
- process.env.TELNYX_PUBLIC_KEY = "public-key";
81
- } else {
82
- process.env.PLIVO_AUTH_ID = "MA123";
83
- process.env.PLIVO_AUTH_TOKEN = "secret";
84
- }
85
- const fromEnv = resolveVoiceCallConfig(createBaseConfig(provider));
86
- expect(validateProviderConfig(fromEnv)).toMatchObject({ valid: true, errors: [] });
87
- }
88
- });
89
- });
90
-
91
- describe("twilio provider", () => {
92
- it("accepts SecretRef-backed auth tokens before runtime resolution", () => {
93
- const config = VoiceCallConfigSchema.parse({
94
- enabled: true,
95
- provider: "twilio",
96
- fromNumber: "+15550001234",
97
- twilio: {
98
- accountSid: "AC123",
99
- authToken: envRef("TWILIO_AUTH_TOKEN"),
100
- },
101
- });
102
-
103
- expect(config.twilio?.authToken).toEqual(envRef("TWILIO_AUTH_TOKEN"));
104
- expect(validateProviderConfig(config)).toMatchObject({ valid: true, errors: [] });
105
- expect(() => resolveTwilioAuthToken(config)).toThrow(
106
- 'plugins.entries.voice-call.config.twilio.authToken: unresolved SecretRef "env:default:TWILIO_AUTH_TOKEN"',
107
- );
108
- });
109
-
110
- it("passes validation with mixed config and env vars", () => {
111
- process.env.TWILIO_AUTH_TOKEN = "secret";
112
- let config = createBaseConfig("twilio");
113
- config.twilio = { accountSid: "AC123" };
114
- config = resolveVoiceCallConfig(config);
115
-
116
- const result = validateProviderConfig(config);
117
-
118
- expect(result.valid).toBe(true);
119
- expect(result.errors).toEqual([]);
120
- });
121
-
122
- it("resolves the Twilio from number from environment", () => {
123
- process.env.TWILIO_ACCOUNT_SID = "AC123";
124
- process.env.TWILIO_AUTH_TOKEN = "secret";
125
- process.env.TWILIO_FROM_NUMBER = "+15550001234";
126
-
127
- const config = resolveVoiceCallConfig({
128
- ...createBaseConfig("twilio"),
129
- fromNumber: undefined,
130
- });
131
-
132
- expect(config.fromNumber).toBe("+15550001234");
133
- expect(validateProviderConfig(config)).toMatchObject({ valid: true, errors: [] });
134
- });
135
-
136
- it("fails validation when required twilio credentials are missing", () => {
137
- process.env.TWILIO_AUTH_TOKEN = "secret";
138
- const missingSid = validateProviderConfig(resolveVoiceCallConfig(createBaseConfig("twilio")));
139
- expect(missingSid.valid).toBe(false);
140
- expect(missingSid.errors).toContain(
141
- "plugins.entries.voice-call.config.twilio.accountSid is required (or set TWILIO_ACCOUNT_SID env)",
142
- );
143
-
144
- delete process.env.TWILIO_AUTH_TOKEN;
145
- process.env.TWILIO_ACCOUNT_SID = "AC123";
146
- const missingToken = validateProviderConfig(
147
- resolveVoiceCallConfig(createBaseConfig("twilio")),
148
- );
149
- expect(missingToken.valid).toBe(false);
150
- expect(missingToken.errors).toContain(
151
- "plugins.entries.voice-call.config.twilio.authToken is required (or set TWILIO_AUTH_TOKEN env)",
152
- );
153
- });
154
- });
155
-
156
- describe("telnyx provider", () => {
157
- it("fails validation when apiKey is missing everywhere", () => {
158
- process.env.TELNYX_CONNECTION_ID = "CONN456";
159
- let config = createBaseConfig("telnyx");
160
- config = resolveVoiceCallConfig(config);
161
-
162
- const result = validateProviderConfig(config);
163
-
164
- expect(result.valid).toBe(false);
165
- expect(result.errors).toContain(
166
- "plugins.entries.voice-call.config.telnyx.apiKey is required (or set TELNYX_API_KEY env)",
167
- );
168
- });
169
-
170
- it("requires a public key unless signature verification is skipped", () => {
171
- const missingPublicKey = createBaseConfig("telnyx");
172
- missingPublicKey.inboundPolicy = "allowlist";
173
- missingPublicKey.telnyx = { apiKey: "KEY123", connectionId: "CONN456" };
174
- const missingPublicKeyResult = validateProviderConfig(missingPublicKey);
175
- expect(missingPublicKeyResult.valid).toBe(false);
176
- expect(missingPublicKeyResult.errors).toContain(
177
- "plugins.entries.voice-call.config.telnyx.publicKey is required (or set TELNYX_PUBLIC_KEY env)",
178
- );
179
-
180
- const withPublicKey = createBaseConfig("telnyx");
181
- withPublicKey.inboundPolicy = "allowlist";
182
- withPublicKey.telnyx = {
183
- apiKey: "KEY123",
184
- connectionId: "CONN456",
185
- publicKey: "public-key",
186
- };
187
- expect(validateProviderConfig(withPublicKey)).toMatchObject({ valid: true, errors: [] });
188
-
189
- const skippedVerification = createBaseConfig("telnyx");
190
- skippedVerification.skipSignatureVerification = true;
191
- skippedVerification.telnyx = { apiKey: "KEY123", connectionId: "CONN456" };
192
- expect(validateProviderConfig(skippedVerification)).toMatchObject({
193
- valid: true,
194
- errors: [],
195
- });
196
- });
197
- });
198
-
199
- describe("plivo provider", () => {
200
- it("fails validation when authId is missing everywhere", () => {
201
- process.env.PLIVO_AUTH_TOKEN = "secret";
202
- let config = createBaseConfig("plivo");
203
- config = resolveVoiceCallConfig(config);
204
-
205
- const result = validateProviderConfig(config);
206
-
207
- expect(result.valid).toBe(false);
208
- expect(result.errors).toContain(
209
- "plugins.entries.voice-call.config.plivo.authId is required (or set PLIVO_AUTH_ID env)",
210
- );
211
- });
212
- });
213
-
214
- describe("disabled config", () => {
215
- it("skips validation when enabled is false", () => {
216
- const config = createBaseConfig("twilio");
217
- config.enabled = false;
218
-
219
- const result = validateProviderConfig(config);
220
-
221
- expect(result.valid).toBe(true);
222
- expect(result.errors).toEqual([]);
223
- });
224
- });
225
-
226
- describe("realtime config", () => {
227
- it("rejects disabled inbound policy for realtime mode", () => {
228
- const config = createBaseConfig("twilio");
229
- config.realtime.enabled = true;
230
- config.inboundPolicy = "disabled";
231
-
232
- const result = validateProviderConfig(config);
233
-
234
- expect(result.valid).toBe(false);
235
- expect(result.errors).toContain(
236
- 'plugins.entries.voice-call.config.inboundPolicy must not be "disabled" when realtime.enabled is true',
237
- );
238
- });
239
-
240
- it("rejects enabling realtime and streaming together", () => {
241
- const config = createBaseConfig("twilio");
242
- config.realtime.enabled = true;
243
- config.streaming.enabled = true;
244
- config.inboundPolicy = "allowlist";
245
-
246
- const result = validateProviderConfig(config);
247
-
248
- expect(result.valid).toBe(false);
249
- expect(result.errors).toContain(
250
- "plugins.entries.voice-call.config.realtime.enabled and plugins.entries.voice-call.config.streaming.enabled cannot both be true",
251
- );
252
- });
253
- });
254
- });
255
-
256
- describe("resolveVoiceCallConfig", () => {
257
- it("enables the pre-answer stale call reaper by default", () => {
258
- const config = resolveVoiceCallConfig({ enabled: true, provider: "mock" });
259
-
260
- expect(config.staleCallReaperSeconds).toBe(120);
261
- });
262
-
263
- it("keeps voice sessions scoped by phone by default", () => {
264
- const config = resolveVoiceCallConfig({ enabled: true, provider: "mock" });
265
-
266
- expect(config.sessionScope).toBe("per-phone");
267
- expect(
268
- resolveVoiceCallSessionKey({
269
- config,
270
- callId: "call-123",
271
- phone: "+1 (555) 000-1111",
272
- }),
273
- ).toBe("voice:15550001111");
274
- });
275
-
276
- it("can scope voice sessions to each call", () => {
277
- const config = resolveVoiceCallConfig({
278
- enabled: true,
279
- provider: "mock",
280
- sessionScope: "per-call",
281
- });
282
-
283
- expect(config.sessionScope).toBe("per-call");
284
- expect(
285
- resolveVoiceCallSessionKey({
286
- config,
287
- callId: "call-123",
288
- phone: "+1 (555) 000-1111",
289
- }),
290
- ).toBe("voice:call:call-123");
291
- });
292
-
293
- it("preserves explicit voice session keys", () => {
294
- const config = resolveVoiceCallConfig({
295
- enabled: true,
296
- provider: "mock",
297
- sessionScope: "per-call",
298
- });
299
-
300
- expect(
301
- resolveVoiceCallSessionKey({
302
- config,
303
- callId: "call-123",
304
- phone: "+1 (555) 000-1111",
305
- explicitSessionKey: "meet-room-1",
306
- }),
307
- ).toBe("meet-room-1");
308
- });
309
-
310
- it("resolves per-number inbound route overrides over global voice settings", () => {
311
- const config = resolveVoiceCallConfig({
312
- enabled: true,
313
- provider: "mock",
314
- inboundGreeting: "Hello from global.",
315
- agentId: "main",
316
- responseModel: "openai/gpt-5.4-mini",
317
- responseSystemPrompt: "Global voice assistant.",
318
- responseTimeoutMs: 10000,
319
- tts: {
320
- provider: "openai",
321
- providers: {
322
- openai: { voice: "coral", speed: 1 },
323
- },
324
- },
325
- numbers: {
326
- "+15550001111": {
327
- inboundGreeting: "Silver Fox Cards, how can I help?",
328
- agentId: "cards",
329
- responseModel: "openai/gpt-5.5",
330
- responseSystemPrompt: "You are a baseball card expert.",
331
- responseTimeoutMs: 20000,
332
- tts: {
333
- providers: {
334
- openai: { voice: "alloy" },
335
- },
336
- },
337
- },
338
- },
339
- });
340
-
341
- expect(resolveVoiceCallNumberRouteKey(config, "+1 (555) 000-1111")).toBe("+15550001111");
342
- const effective = resolveVoiceCallEffectiveConfig(config, "+1 (555) 000-1111");
343
-
344
- expect(effective.numberRouteKey).toBe("+15550001111");
345
- expect(effective.config.inboundGreeting).toBe("Silver Fox Cards, how can I help?");
346
- expect(effective.config.agentId).toBe("cards");
347
- expect(effective.config.responseModel).toBe("openai/gpt-5.5");
348
- expect(effective.config.responseSystemPrompt).toBe("You are a baseball card expert.");
349
- expect(effective.config.responseTimeoutMs).toBe(20000);
350
- expect(effective.config.tts?.provider).toBe("openai");
351
- expect(effective.config.tts?.providers?.openai).toEqual({ voice: "alloy", speed: 1 });
352
- });
353
-
354
- it("falls back to global voice settings when no per-number route matches", () => {
355
- const config = resolveVoiceCallConfig({
356
- enabled: true,
357
- provider: "mock",
358
- inboundGreeting: "Hello from global.",
359
- numbers: {
360
- "+15550001111": {
361
- inboundGreeting: "Hello from route.",
362
- },
363
- },
364
- });
365
-
366
- const effective = resolveVoiceCallEffectiveConfig(config, "+15550002222");
367
-
368
- expect(effective.numberRouteKey).toBeUndefined();
369
- expect(effective.config).toBe(config);
370
- expect(effective.config.inboundGreeting).toBe("Hello from global.");
371
- });
372
- });
373
-
374
- describe("normalizeVoiceCallConfig", () => {
375
- it("fills nested runtime defaults from a partial config boundary", () => {
376
- const normalized = normalizeVoiceCallConfig({
377
- enabled: true,
378
- provider: "mock",
379
- streaming: {
380
- enabled: true,
381
- streamPath: "/custom-stream",
382
- },
383
- });
384
-
385
- expect(normalized.serve.path).toBe("/voice/webhook");
386
- expect(normalized.streaming.streamPath).toBe("/custom-stream");
387
- expect(normalized.streaming.provider).toBeUndefined();
388
- expect(normalized.streaming.providers).toEqual({});
389
- expect(normalized.realtime.streamPath).toBe("/voice/stream/realtime");
390
- expect(normalized.realtime.toolPolicy).toBe("safe-read-only");
391
- expect(normalized.realtime.fastContext).toEqual({
392
- enabled: false,
393
- timeoutMs: 800,
394
- maxResults: 3,
395
- sources: ["memory", "sessions"],
396
- fallbackToConsult: false,
397
- });
398
- expect(normalized.realtime.instructions).toContain("openclaw_agent_consult");
399
- expect(normalized.tunnel.provider).toBe("none");
400
- expect(normalized.webhookSecurity.allowedHosts).toEqual([]);
401
- });
402
-
403
- it("derives the realtime stream path from a custom webhook path", () => {
404
- const normalized = normalizeVoiceCallConfig({
405
- enabled: true,
406
- provider: "twilio",
407
- serve: {
408
- path: "/custom/webhook",
409
- },
410
- });
411
-
412
- expect(normalized.realtime.streamPath).toBe("/custom/stream/realtime");
413
- });
414
-
415
- it("accepts partial nested TTS overrides and preserves nested objects", () => {
416
- const normalized = normalizeVoiceCallConfig({
417
- tts: {
418
- provider: "elevenlabs",
419
- providers: {
420
- elevenlabs: {
421
- apiKey: {
422
- source: "env",
423
- provider: "elevenlabs",
424
- id: "ELEVENLABS_API_KEY",
425
- },
426
- voiceSettings: {
427
- speed: 1.1,
428
- },
429
- },
430
- },
431
- },
432
- });
433
-
434
- const { tts, elevenlabs } = requireElevenLabsTtsConfig(normalized);
435
- expect(tts.provider).toBe("elevenlabs");
436
- expect(elevenlabs.apiKey).toEqual({
437
- source: "env",
438
- provider: "elevenlabs",
439
- id: "ELEVENLABS_API_KEY",
440
- });
441
- expect(elevenlabs.voiceSettings).toEqual({ speed: 1.1 });
442
- });
443
- });
444
-
445
- describe("resolveVoiceCallConfig", () => {
446
- it("preserves configured realtime instructions without env indirection", () => {
447
- const resolved = resolveVoiceCallConfig({
448
- enabled: true,
449
- provider: "twilio",
450
- realtime: {
451
- enabled: true,
452
- instructions: "Stay concise.",
453
- },
454
- });
455
-
456
- expect(resolved.realtime.instructions).toBe("Stay concise.");
457
- expect(resolved.realtime.toolPolicy).toBe("safe-read-only");
458
- expect(resolved.realtime.provider).toBeUndefined();
459
- });
460
-
461
- it("leaves responseModel unset so voice responses can inherit runtime defaults", () => {
462
- const resolved = resolveVoiceCallConfig({
463
- enabled: true,
464
- provider: "mock",
465
- });
466
-
467
- expect(resolved.responseModel).toBeUndefined();
468
- });
469
-
470
- it("preserves the configured voice response agent id", () => {
471
- const resolved = resolveVoiceCallConfig({
472
- enabled: true,
473
- provider: "mock",
474
- agentId: "voice",
475
- });
476
-
477
- expect(resolved.agentId).toBe("voice");
478
- });
479
- });