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