@vellumai/assistant 0.4.23 → 0.4.26
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.
- package/bun.lock +3 -0
- package/package.json +2 -1
- package/src/__tests__/__snapshots__/ipc-snapshot.test.ts.snap +0 -15
- package/src/__tests__/assistant-events-sse-hardening.test.ts +9 -3
- package/src/__tests__/call-controller.test.ts +80 -0
- package/src/__tests__/config-schema.test.ts +38 -178
- package/src/__tests__/conversation-routes-guardian-reply.test.ts +4 -1
- package/src/__tests__/credential-security-invariants.test.ts +0 -2
- package/src/__tests__/guardian-verify-setup-skill-regression.test.ts +2 -2
- package/src/__tests__/ipc-snapshot.test.ts +0 -9
- package/src/__tests__/onboarding-template-contract.test.ts +10 -20
- package/src/__tests__/relay-server.test.ts +3 -3
- package/src/__tests__/runtime-events-sse-parity.test.ts +10 -0
- package/src/__tests__/runtime-events-sse.test.ts +7 -0
- package/src/__tests__/session-runtime-assembly.test.ts +34 -8
- package/src/__tests__/system-prompt.test.ts +7 -1
- package/src/__tests__/trusted-contact-approval-notifier.test.ts +12 -8
- package/src/__tests__/twilio-routes-twiml.test.ts +2 -2
- package/src/__tests__/twilio-routes.test.ts +2 -3
- package/src/__tests__/voice-quality.test.ts +21 -132
- package/src/calls/call-controller.ts +34 -29
- package/src/calls/relay-server.ts +11 -5
- package/src/calls/twilio-routes.ts +4 -38
- package/src/calls/voice-quality.ts +7 -63
- package/src/config/bundled-skills/guardian-verify-setup/SKILL.md +7 -10
- package/src/config/bundled-skills/messaging/SKILL.md +3 -5
- package/src/config/bundled-skills/phone-calls/SKILL.md +144 -83
- package/src/config/bundled-skills/sms-setup/SKILL.md +0 -20
- package/src/config/bundled-skills/twilio-setup/SKILL.md +9 -17
- package/src/config/bundled-skills/voice-setup/SKILL.md +36 -1
- package/src/config/bundled-skills/voice-setup/icon.svg +20 -0
- package/src/config/calls-schema.ts +3 -53
- package/src/config/elevenlabs-schema.ts +33 -0
- package/src/config/schema.ts +183 -137
- package/src/config/types.ts +0 -1
- package/src/daemon/handlers/browser.ts +1 -6
- package/src/daemon/ipc-contract/browser.ts +5 -14
- package/src/daemon/ipc-contract-inventory.json +0 -2
- package/src/daemon/session-agent-loop-handlers.ts +3 -0
- package/src/daemon/session-runtime-assembly.ts +9 -7
- package/src/mcp/client.ts +2 -1
- package/src/memory/conversation-crud.ts +339 -166
- package/src/runtime/auth/middleware.ts +87 -26
- package/src/runtime/routes/events-routes.ts +7 -0
- package/src/runtime/routes/inbound-message-handler.ts +3 -4
- package/src/schedule/scheduler.ts +159 -45
- package/src/security/secure-keys.ts +3 -3
- package/src/tools/browser/browser-manager.ts +72 -228
- package/src/tools/browser/browser-screencast.ts +0 -5
- package/src/tools/network/script-proxy/certs.ts +7 -237
- package/src/tools/network/script-proxy/connect-tunnel.ts +1 -82
- package/src/tools/network/script-proxy/http-forwarder.ts +2 -151
- package/src/tools/network/script-proxy/logging.ts +12 -196
- package/src/tools/network/script-proxy/mitm-handler.ts +2 -270
- package/src/tools/network/script-proxy/policy.ts +4 -152
- package/src/tools/network/script-proxy/router.ts +2 -60
- package/src/tools/network/script-proxy/server.ts +5 -137
- package/src/tools/network/script-proxy/types.ts +19 -125
- package/src/tools/system/voice-config.ts +23 -1
- package/src/util/logger.ts +4 -1
- package/src/__tests__/elevenlabs-config.test.ts +0 -95
- package/src/__tests__/twilio-routes-elevenlabs.test.ts +0 -407
- package/src/calls/elevenlabs-config.ts +0 -32
|
@@ -1,407 +0,0 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* Handler-level tests for ElevenLabs voice webhook branches in handleVoiceWebhook.
|
|
3
|
-
*
|
|
4
|
-
* Tests the WS-A (invalid profile + fallback semantics) and WS-B
|
|
5
|
-
* (elevenlabs_agent guard) paths by mocking resolveVoiceQualityProfile
|
|
6
|
-
* to return specific profiles and asserting on HTTP response status/body.
|
|
7
|
-
*/
|
|
8
|
-
import { mkdtempSync, realpathSync, rmSync } from "node:fs";
|
|
9
|
-
import { tmpdir } from "node:os";
|
|
10
|
-
import { join } from "node:path";
|
|
11
|
-
import { afterAll, beforeEach, describe, expect, mock, test } from "bun:test";
|
|
12
|
-
|
|
13
|
-
mock.module("../config/env.js", () => ({ isHttpAuthDisabled: () => true }));
|
|
14
|
-
|
|
15
|
-
const testDir = realpathSync(
|
|
16
|
-
mkdtempSync(join(tmpdir(), "twilio-routes-11labs-test-")),
|
|
17
|
-
);
|
|
18
|
-
|
|
19
|
-
mock.module("../util/platform.js", () => ({
|
|
20
|
-
getRootDir: () => testDir,
|
|
21
|
-
getDataDir: () => testDir,
|
|
22
|
-
isMacOS: () => process.platform === "darwin",
|
|
23
|
-
isLinux: () => process.platform === "linux",
|
|
24
|
-
isWindows: () => process.platform === "win32",
|
|
25
|
-
getSocketPath: () => join(testDir, "test.sock"),
|
|
26
|
-
getPidPath: () => join(testDir, "test.pid"),
|
|
27
|
-
getDbPath: () => join(testDir, "test.db"),
|
|
28
|
-
getLogPath: () => join(testDir, "test.log"),
|
|
29
|
-
ensureDataDir: () => {},
|
|
30
|
-
}));
|
|
31
|
-
|
|
32
|
-
mock.module("../util/logger.js", () => ({
|
|
33
|
-
getLogger: () =>
|
|
34
|
-
new Proxy({} as Record<string, unknown>, {
|
|
35
|
-
get: () => () => {},
|
|
36
|
-
}),
|
|
37
|
-
}));
|
|
38
|
-
|
|
39
|
-
mock.module("../config/loader.js", () => ({
|
|
40
|
-
getConfig: () => ({
|
|
41
|
-
ui: {},
|
|
42
|
-
|
|
43
|
-
model: "test",
|
|
44
|
-
provider: "test",
|
|
45
|
-
apiKeys: {},
|
|
46
|
-
memory: { enabled: false },
|
|
47
|
-
rateLimit: { maxRequestsPerMinute: 0, maxTokensPerSession: 0 },
|
|
48
|
-
secretDetection: { enabled: false },
|
|
49
|
-
}),
|
|
50
|
-
loadConfig: () => ({
|
|
51
|
-
model: "test",
|
|
52
|
-
provider: "test",
|
|
53
|
-
apiKeys: {},
|
|
54
|
-
memory: { enabled: false },
|
|
55
|
-
rateLimit: { maxRequestsPerMinute: 0, maxTokensPerSession: 0 },
|
|
56
|
-
secretDetection: { enabled: false },
|
|
57
|
-
calls: {
|
|
58
|
-
voice: {
|
|
59
|
-
mode: "twilio_standard",
|
|
60
|
-
language: "en-US",
|
|
61
|
-
transcriptionProvider: "Deepgram",
|
|
62
|
-
fallbackToStandardOnError: true,
|
|
63
|
-
elevenlabs: {
|
|
64
|
-
voiceId: "",
|
|
65
|
-
voiceModelId: "",
|
|
66
|
-
speed: 1.0,
|
|
67
|
-
stability: 0.5,
|
|
68
|
-
similarityBoost: 0.75,
|
|
69
|
-
useSpeakerBoost: true,
|
|
70
|
-
agentId: "",
|
|
71
|
-
apiBaseUrl: "https://api.elevenlabs.io",
|
|
72
|
-
registerCallTimeoutMs: 5000,
|
|
73
|
-
},
|
|
74
|
-
},
|
|
75
|
-
},
|
|
76
|
-
ingress: { enabled: false, publicBaseUrl: "" },
|
|
77
|
-
}),
|
|
78
|
-
}));
|
|
79
|
-
|
|
80
|
-
mock.module("../security/secure-keys.js", () => ({
|
|
81
|
-
getSecureKey: () => undefined,
|
|
82
|
-
}));
|
|
83
|
-
|
|
84
|
-
mock.module("../calls/twilio-provider.js", () => ({
|
|
85
|
-
TwilioConversationRelayProvider: class {
|
|
86
|
-
readonly name = "twilio";
|
|
87
|
-
static getAuthToken(): string | null {
|
|
88
|
-
return null;
|
|
89
|
-
}
|
|
90
|
-
static verifyWebhookSignature(): boolean {
|
|
91
|
-
return true;
|
|
92
|
-
}
|
|
93
|
-
async initiateCall() {
|
|
94
|
-
return { callSid: "CA_mock_test" };
|
|
95
|
-
}
|
|
96
|
-
async endCall() {
|
|
97
|
-
return;
|
|
98
|
-
}
|
|
99
|
-
},
|
|
100
|
-
}));
|
|
101
|
-
|
|
102
|
-
mock.module("../calls/twilio-config.js", () => ({
|
|
103
|
-
getTwilioConfig: () => ({
|
|
104
|
-
accountSid: "AC_test",
|
|
105
|
-
authToken: "test-auth-token",
|
|
106
|
-
phoneNumber: "+15550001111",
|
|
107
|
-
webhookBaseUrl: "https://test.example.com",
|
|
108
|
-
wssBaseUrl: "wss://test.example.com",
|
|
109
|
-
}),
|
|
110
|
-
}));
|
|
111
|
-
|
|
112
|
-
// Mock ElevenLabs client — should never be called when guard is active.
|
|
113
|
-
// If any test path reaches register-call, the mock will throw to fail the test.
|
|
114
|
-
const mockRegisterCall = mock(() => {
|
|
115
|
-
throw new Error("register-call should not be reached while guard is active");
|
|
116
|
-
});
|
|
117
|
-
|
|
118
|
-
mock.module("../calls/elevenlabs-client.js", () => ({
|
|
119
|
-
ElevenLabsClient: class {
|
|
120
|
-
registerCall = mockRegisterCall;
|
|
121
|
-
},
|
|
122
|
-
}));
|
|
123
|
-
|
|
124
|
-
mock.module("../calls/elevenlabs-config.js", () => ({
|
|
125
|
-
getElevenLabsConfig: () => ({
|
|
126
|
-
apiBaseUrl: "https://api.elevenlabs.io",
|
|
127
|
-
apiKey: "test-key",
|
|
128
|
-
agentId: "agent-abc",
|
|
129
|
-
registerCallTimeoutMs: 5000,
|
|
130
|
-
}),
|
|
131
|
-
}));
|
|
132
|
-
|
|
133
|
-
// Mock resolveVoiceQualityProfile and isVoiceProfileValid so we can control
|
|
134
|
-
// the profile returned to handleVoiceWebhook per-test.
|
|
135
|
-
import type { VoiceQualityProfile } from "../calls/voice-quality.js";
|
|
136
|
-
|
|
137
|
-
let mockProfile: VoiceQualityProfile = {
|
|
138
|
-
mode: "twilio_standard",
|
|
139
|
-
language: "en-US",
|
|
140
|
-
transcriptionProvider: "Deepgram",
|
|
141
|
-
ttsProvider: "Google",
|
|
142
|
-
voice: "Google.en-US-Journey-O",
|
|
143
|
-
fallbackToStandardOnError: true,
|
|
144
|
-
validationErrors: [],
|
|
145
|
-
};
|
|
146
|
-
|
|
147
|
-
const mockResolveVoiceQualityProfile = mock(() => mockProfile);
|
|
148
|
-
|
|
149
|
-
mock.module("../calls/voice-quality.js", () => ({
|
|
150
|
-
resolveVoiceQualityProfile: mockResolveVoiceQualityProfile,
|
|
151
|
-
isVoiceProfileValid: (profile: VoiceQualityProfile) =>
|
|
152
|
-
profile.validationErrors.length === 0,
|
|
153
|
-
}));
|
|
154
|
-
|
|
155
|
-
// Mock the ingress URL to avoid config lookup issues
|
|
156
|
-
mock.module("../inbound/public-ingress-urls.js", () => ({
|
|
157
|
-
getTwilioRelayUrl: () => "wss://test.example.com/v1/calls/relay",
|
|
158
|
-
getPublicBaseUrl: () => "https://test.example.com",
|
|
159
|
-
}));
|
|
160
|
-
|
|
161
|
-
import { createCallSession, updateCallSession } from "../calls/call-store.js";
|
|
162
|
-
import { handleVoiceWebhook } from "../calls/twilio-routes.js";
|
|
163
|
-
import { getDb, initializeDb, resetDb } from "../memory/db.js";
|
|
164
|
-
import { conversations } from "../memory/schema.js";
|
|
165
|
-
|
|
166
|
-
initializeDb();
|
|
167
|
-
|
|
168
|
-
// ── Helpers ────────────────────────────────────────────────────────────
|
|
169
|
-
|
|
170
|
-
let ensuredConvIds = new Set<string>();
|
|
171
|
-
|
|
172
|
-
function ensureConversation(id: string): void {
|
|
173
|
-
if (ensuredConvIds.has(id)) return;
|
|
174
|
-
const db = getDb();
|
|
175
|
-
const now = Date.now();
|
|
176
|
-
db.insert(conversations)
|
|
177
|
-
.values({
|
|
178
|
-
id,
|
|
179
|
-
title: `Test conversation ${id}`,
|
|
180
|
-
createdAt: now,
|
|
181
|
-
updatedAt: now,
|
|
182
|
-
})
|
|
183
|
-
.run();
|
|
184
|
-
ensuredConvIds.add(id);
|
|
185
|
-
}
|
|
186
|
-
|
|
187
|
-
function resetTables() {
|
|
188
|
-
const db = getDb();
|
|
189
|
-
db.run("DELETE FROM guardian_action_deliveries");
|
|
190
|
-
db.run("DELETE FROM guardian_action_requests");
|
|
191
|
-
db.run("DELETE FROM processed_callbacks");
|
|
192
|
-
db.run("DELETE FROM call_pending_questions");
|
|
193
|
-
db.run("DELETE FROM call_events");
|
|
194
|
-
db.run("DELETE FROM call_sessions");
|
|
195
|
-
db.run("DELETE FROM messages");
|
|
196
|
-
db.run("DELETE FROM conversations");
|
|
197
|
-
ensuredConvIds = new Set();
|
|
198
|
-
}
|
|
199
|
-
|
|
200
|
-
function createTestSession(convId: string, callSid: string) {
|
|
201
|
-
ensureConversation(convId);
|
|
202
|
-
const session = createCallSession({
|
|
203
|
-
conversationId: convId,
|
|
204
|
-
provider: "twilio",
|
|
205
|
-
fromNumber: "+15550001111",
|
|
206
|
-
toNumber: "+15559998888",
|
|
207
|
-
task: "test task",
|
|
208
|
-
});
|
|
209
|
-
updateCallSession(session.id, { providerCallSid: callSid });
|
|
210
|
-
return session;
|
|
211
|
-
}
|
|
212
|
-
|
|
213
|
-
function makeVoiceRequest(
|
|
214
|
-
sessionId: string,
|
|
215
|
-
params: Record<string, string>,
|
|
216
|
-
): Request {
|
|
217
|
-
return new Request(
|
|
218
|
-
`http://127.0.0.1/v1/calls/twilio/voice-webhook?callSessionId=${sessionId}`,
|
|
219
|
-
{
|
|
220
|
-
method: "POST",
|
|
221
|
-
headers: { "Content-Type": "application/x-www-form-urlencoded" },
|
|
222
|
-
body: new URLSearchParams(params).toString(),
|
|
223
|
-
},
|
|
224
|
-
);
|
|
225
|
-
}
|
|
226
|
-
|
|
227
|
-
// ── Tests ──────────────────────────────────────────────────────────────
|
|
228
|
-
|
|
229
|
-
describe("handleVoiceWebhook ElevenLabs branches", () => {
|
|
230
|
-
beforeEach(() => {
|
|
231
|
-
resetTables();
|
|
232
|
-
// Reset mock to default implementation
|
|
233
|
-
mockResolveVoiceQualityProfile.mockReset();
|
|
234
|
-
mockRegisterCall.mockClear();
|
|
235
|
-
// Reset to standard default profile between tests
|
|
236
|
-
mockProfile = {
|
|
237
|
-
mode: "twilio_standard",
|
|
238
|
-
language: "en-US",
|
|
239
|
-
transcriptionProvider: "Deepgram",
|
|
240
|
-
ttsProvider: "Google",
|
|
241
|
-
voice: "Google.en-US-Journey-O",
|
|
242
|
-
fallbackToStandardOnError: true,
|
|
243
|
-
validationErrors: [],
|
|
244
|
-
};
|
|
245
|
-
mockResolveVoiceQualityProfile.mockImplementation(() => mockProfile);
|
|
246
|
-
});
|
|
247
|
-
|
|
248
|
-
afterAll(() => {
|
|
249
|
-
resetDb();
|
|
250
|
-
try {
|
|
251
|
-
rmSync(testDir, { recursive: true, force: true });
|
|
252
|
-
} catch {
|
|
253
|
-
/* best effort */
|
|
254
|
-
}
|
|
255
|
-
});
|
|
256
|
-
|
|
257
|
-
// ── WS-A: Invalid config + fallback disabled => 500 ────────────────
|
|
258
|
-
test("twilio_elevenlabs_tts invalid config with fallback disabled returns 500", async () => {
|
|
259
|
-
mockProfile = {
|
|
260
|
-
mode: "twilio_elevenlabs_tts",
|
|
261
|
-
language: "en-US",
|
|
262
|
-
transcriptionProvider: "Deepgram",
|
|
263
|
-
ttsProvider: "ElevenLabs",
|
|
264
|
-
voice: "",
|
|
265
|
-
fallbackToStandardOnError: false,
|
|
266
|
-
validationErrors: ["voiceId is required"],
|
|
267
|
-
};
|
|
268
|
-
|
|
269
|
-
const session = createTestSession("conv-11labs-1", "CA_11labs_1");
|
|
270
|
-
const req = makeVoiceRequest(session.id, { CallSid: "CA_11labs_1" });
|
|
271
|
-
|
|
272
|
-
const res = await handleVoiceWebhook(req);
|
|
273
|
-
|
|
274
|
-
expect(res.status).toBe(500);
|
|
275
|
-
const body = await res.text();
|
|
276
|
-
expect(body).toContain("Voice quality configuration error");
|
|
277
|
-
expect(body).toContain("voiceId is required");
|
|
278
|
-
});
|
|
279
|
-
|
|
280
|
-
// ── WS-A: Invalid config + fallback enabled => standard TwiML ──────
|
|
281
|
-
test("twilio_elevenlabs_tts invalid config with fallback enabled returns standard TwiML", async () => {
|
|
282
|
-
// When fallback is enabled and voiceId is missing, resolveVoiceQualityProfile
|
|
283
|
-
// falls back to standard mode but retains validation errors.
|
|
284
|
-
mockProfile = {
|
|
285
|
-
mode: "twilio_standard",
|
|
286
|
-
language: "en-US",
|
|
287
|
-
transcriptionProvider: "Deepgram",
|
|
288
|
-
ttsProvider: "Google",
|
|
289
|
-
voice: "Google.en-US-Journey-O",
|
|
290
|
-
fallbackToStandardOnError: true,
|
|
291
|
-
validationErrors: [
|
|
292
|
-
"calls.voice.elevenlabs.voiceId is empty; falling back to twilio_standard",
|
|
293
|
-
],
|
|
294
|
-
};
|
|
295
|
-
|
|
296
|
-
const session = createTestSession("conv-11labs-2", "CA_11labs_2");
|
|
297
|
-
const req = makeVoiceRequest(session.id, { CallSid: "CA_11labs_2" });
|
|
298
|
-
|
|
299
|
-
const res = await handleVoiceWebhook(req);
|
|
300
|
-
|
|
301
|
-
expect(res.status).toBe(200);
|
|
302
|
-
const twiml = await res.text();
|
|
303
|
-
expect(twiml).toContain('ttsProvider="Google"');
|
|
304
|
-
expect(twiml).toContain("<ConversationRelay");
|
|
305
|
-
expect(twiml).toContain('voice="Google.en-US-Journey-O"');
|
|
306
|
-
});
|
|
307
|
-
|
|
308
|
-
// ── WS-B: elevenlabs_agent strict mode (fallback false) => 501 ─────
|
|
309
|
-
test("elevenlabs_agent with fallback disabled returns 501", async () => {
|
|
310
|
-
mockProfile = {
|
|
311
|
-
mode: "elevenlabs_agent",
|
|
312
|
-
language: "en-US",
|
|
313
|
-
transcriptionProvider: "Deepgram",
|
|
314
|
-
ttsProvider: "ElevenLabs",
|
|
315
|
-
voice: "voice123-turbo_v2_5-1_0.5_0.75",
|
|
316
|
-
agentId: "agent-abc",
|
|
317
|
-
fallbackToStandardOnError: false,
|
|
318
|
-
validationErrors: [],
|
|
319
|
-
};
|
|
320
|
-
|
|
321
|
-
const session = createTestSession("conv-11labs-3", "CA_11labs_3");
|
|
322
|
-
const req = makeVoiceRequest(session.id, { CallSid: "CA_11labs_3" });
|
|
323
|
-
|
|
324
|
-
const res = await handleVoiceWebhook(req);
|
|
325
|
-
|
|
326
|
-
expect(res.status).toBe(501);
|
|
327
|
-
const body = await res.text();
|
|
328
|
-
expect(body).toContain("consultation bridging");
|
|
329
|
-
expect(body).toContain("elevenlabs_agent mode is restricted");
|
|
330
|
-
});
|
|
331
|
-
|
|
332
|
-
// ── WS-B: elevenlabs_agent with fallback true => standard TwiML ────
|
|
333
|
-
test("elevenlabs_agent with fallback enabled returns standard TwiML", async () => {
|
|
334
|
-
// First call returns the elevenlabs_agent profile (triggers the guard)
|
|
335
|
-
mockResolveVoiceQualityProfile.mockImplementationOnce(() => ({
|
|
336
|
-
mode: "elevenlabs_agent" as const,
|
|
337
|
-
language: "en-US",
|
|
338
|
-
transcriptionProvider: "Deepgram",
|
|
339
|
-
ttsProvider: "ElevenLabs",
|
|
340
|
-
voice: "voice123-turbo_v2_5-1_0.5_0.75",
|
|
341
|
-
agentId: "agent-abc",
|
|
342
|
-
fallbackToStandardOnError: true,
|
|
343
|
-
validationErrors: [],
|
|
344
|
-
}));
|
|
345
|
-
// Second call returns the standard profile (used for TwiML generation)
|
|
346
|
-
mockResolveVoiceQualityProfile.mockImplementationOnce(() => ({
|
|
347
|
-
mode: "twilio_standard" as const,
|
|
348
|
-
language: "en-US",
|
|
349
|
-
transcriptionProvider: "Deepgram",
|
|
350
|
-
ttsProvider: "Google",
|
|
351
|
-
voice: "Google.en-US-Journey-O",
|
|
352
|
-
fallbackToStandardOnError: true,
|
|
353
|
-
validationErrors: [],
|
|
354
|
-
}));
|
|
355
|
-
|
|
356
|
-
const session = createTestSession("conv-11labs-4", "CA_11labs_4");
|
|
357
|
-
const req = makeVoiceRequest(session.id, { CallSid: "CA_11labs_4" });
|
|
358
|
-
|
|
359
|
-
const res = await handleVoiceWebhook(req);
|
|
360
|
-
|
|
361
|
-
expect(res.status).toBe(200);
|
|
362
|
-
const twiml = await res.text();
|
|
363
|
-
// When elevenlabs_agent is guarded with fallback enabled, the handler
|
|
364
|
-
// replaces the profile with a standard twilio_standard profile and
|
|
365
|
-
// generates TwiML with Google TTS instead of ElevenLabs.
|
|
366
|
-
expect(twiml).toContain("<ConversationRelay");
|
|
367
|
-
expect(twiml).toContain('<?xml version="1.0" encoding="UTF-8"?>');
|
|
368
|
-
expect(twiml).toContain("<Response>");
|
|
369
|
-
expect(twiml).toContain("<Connect>");
|
|
370
|
-
expect(twiml).toContain('ttsProvider="Google"');
|
|
371
|
-
expect(twiml).toContain('voice="Google.en-US-Journey-O"');
|
|
372
|
-
});
|
|
373
|
-
|
|
374
|
-
// ── Guard prevents register-call attempt ────────────────────────────
|
|
375
|
-
test("guarded elevenlabs_agent does not attempt ElevenLabs register-call", async () => {
|
|
376
|
-
// Configure as elevenlabs_agent with fallback (guard will fire)
|
|
377
|
-
mockResolveVoiceQualityProfile.mockImplementationOnce(() => ({
|
|
378
|
-
mode: "elevenlabs_agent" as const,
|
|
379
|
-
language: "en-US",
|
|
380
|
-
transcriptionProvider: "Deepgram",
|
|
381
|
-
ttsProvider: "ElevenLabs",
|
|
382
|
-
voice: "voice123-turbo_v2_5-1_0.5_0.75",
|
|
383
|
-
agentId: "agent-abc",
|
|
384
|
-
fallbackToStandardOnError: true,
|
|
385
|
-
validationErrors: [],
|
|
386
|
-
}));
|
|
387
|
-
mockResolveVoiceQualityProfile.mockImplementationOnce(() => ({
|
|
388
|
-
mode: "twilio_standard" as const,
|
|
389
|
-
language: "en-US",
|
|
390
|
-
transcriptionProvider: "Deepgram",
|
|
391
|
-
ttsProvider: "Google",
|
|
392
|
-
voice: "Google.en-US-Journey-O",
|
|
393
|
-
fallbackToStandardOnError: true,
|
|
394
|
-
validationErrors: [],
|
|
395
|
-
}));
|
|
396
|
-
|
|
397
|
-
const session = createTestSession("conv-11labs-5", "CA_11labs_5");
|
|
398
|
-
const req = makeVoiceRequest(session.id, { CallSid: "CA_11labs_5" });
|
|
399
|
-
|
|
400
|
-
const res = await handleVoiceWebhook(req);
|
|
401
|
-
|
|
402
|
-
expect(res.status).toBe(200);
|
|
403
|
-
// The ElevenLabs register-call mock was never invoked — the guard
|
|
404
|
-
// blocked the entire mode before any ElevenLabs API interaction.
|
|
405
|
-
expect(mockRegisterCall).not.toHaveBeenCalled();
|
|
406
|
-
});
|
|
407
|
-
});
|
|
@@ -1,32 +0,0 @@
|
|
|
1
|
-
import { getConfig } from '../config/loader.js';
|
|
2
|
-
import { getSecureKey } from '../security/secure-keys.js';
|
|
3
|
-
import { ConfigError } from '../util/errors.js';
|
|
4
|
-
|
|
5
|
-
export interface ElevenLabsConfig {
|
|
6
|
-
apiKey: string;
|
|
7
|
-
apiBaseUrl: string;
|
|
8
|
-
agentId: string;
|
|
9
|
-
registerCallTimeoutMs: number;
|
|
10
|
-
}
|
|
11
|
-
|
|
12
|
-
export function getElevenLabsConfig(): ElevenLabsConfig {
|
|
13
|
-
const config = getConfig();
|
|
14
|
-
const voice = config.calls.voice;
|
|
15
|
-
|
|
16
|
-
const apiKey = getSecureKey('credential:elevenlabs:api_key') ?? '';
|
|
17
|
-
if (!apiKey) {
|
|
18
|
-
throw new ConfigError('ElevenLabs API key is not configured. Set credential:elevenlabs:api_key in the secure key store.');
|
|
19
|
-
}
|
|
20
|
-
|
|
21
|
-
const agentId = voice.elevenlabs.agentId;
|
|
22
|
-
if (!agentId) {
|
|
23
|
-
throw new ConfigError('ElevenLabs agent ID is not configured. Set calls.voice.elevenlabs.agentId in config.');
|
|
24
|
-
}
|
|
25
|
-
|
|
26
|
-
return {
|
|
27
|
-
apiKey,
|
|
28
|
-
apiBaseUrl: voice.elevenlabs.apiBaseUrl,
|
|
29
|
-
agentId,
|
|
30
|
-
registerCallTimeoutMs: voice.elevenlabs.registerCallTimeoutMs,
|
|
31
|
-
};
|
|
32
|
-
}
|