@absolutejs/voice 0.0.22-beta.506 → 0.0.22-beta.508

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/dist/index.js CHANGED
@@ -48011,6 +48011,556 @@ var createVoiceLiveMonitorRoutes = (options) => {
48011
48011
  }
48012
48012
  return app;
48013
48013
  };
48014
+ // src/costPredictor.ts
48015
+ var lookupRates2 = (priceBook, provider, model) => {
48016
+ if (!provider)
48017
+ return;
48018
+ const namespacedKey = model ? `${provider.toLowerCase()}:${model.toLowerCase()}` : undefined;
48019
+ if (namespacedKey && priceBook[namespacedKey]) {
48020
+ return priceBook[namespacedKey];
48021
+ }
48022
+ const bare = provider.toLowerCase();
48023
+ return priceBook[bare];
48024
+ };
48025
+ var round = (value, places = 6) => {
48026
+ const factor = 10 ** places;
48027
+ return Math.round(value * factor) / factor;
48028
+ };
48029
+ var predictVoiceCallCost = (input) => {
48030
+ const priceBook = input.priceBook ?? DEFAULT_VOICE_PRICE_BOOK;
48031
+ const p = input.profile;
48032
+ const llm = lookupRates2(priceBook, p.llmProvider, p.llmModel)?.llm;
48033
+ const tts = p.ttsProvider ? lookupRates2(priceBook, p.ttsProvider, p.ttsModel)?.tts : undefined;
48034
+ const stt = p.sttProvider ? lookupRates2(priceBook, p.sttProvider, p.sttModel)?.stt : undefined;
48035
+ const telephony = p.telephonyProvider ? lookupRates2(priceBook, p.telephonyProvider, undefined)?.telephony : undefined;
48036
+ const totalInputTokens = p.inputTokensPerTurn * p.turnsPerCall;
48037
+ const totalOutputTokens = p.outputTokensPerTurn * p.turnsPerCall;
48038
+ const llmUsd = llm ? totalInputTokens * llm.inputPerMillionTokensUsd / 1e6 + totalOutputTokens * llm.outputPerMillionTokensUsd / 1e6 : 0;
48039
+ const totalChars = p.ttsCharsPerTurn * p.turnsPerCall;
48040
+ let ttsUsd = 0;
48041
+ if (tts?.perMillionCharactersUsd !== undefined && totalChars > 0) {
48042
+ ttsUsd = totalChars * tts.perMillionCharactersUsd / 1e6;
48043
+ } else if (tts?.perSecondUsd !== undefined) {
48044
+ const audioSec = totalChars / 13.75;
48045
+ ttsUsd = audioSec * tts.perSecondUsd;
48046
+ }
48047
+ const sttSeconds = p.minutesPerCall * 60;
48048
+ const sttUsd = stt ? sttSeconds * stt.perSecondUsd : 0;
48049
+ const telephonyUsd = telephony ? p.minutesPerCall * telephony.perMinuteUsd : 0;
48050
+ const totalPerCall = llmUsd + ttsUsd + sttUsd + telephonyUsd;
48051
+ const callsPerDay = (p.inboundPerDay ?? 0) + (p.outboundPerDay ?? 0);
48052
+ const monthlyCalls = callsPerDay * 30;
48053
+ return {
48054
+ callsPerDay,
48055
+ monthly: {
48056
+ llmUsd: round(llmUsd * monthlyCalls),
48057
+ sttUsd: round(sttUsd * monthlyCalls),
48058
+ telephonyUsd: round(telephonyUsd * monthlyCalls),
48059
+ totalUsd: round(totalPerCall * monthlyCalls),
48060
+ ttsUsd: round(ttsUsd * monthlyCalls)
48061
+ },
48062
+ perCall: {
48063
+ llmUsd: round(llmUsd),
48064
+ sttUsd: round(sttUsd),
48065
+ telephonyUsd: round(telephonyUsd),
48066
+ totalUsd: round(totalPerCall),
48067
+ ttsUsd: round(ttsUsd)
48068
+ }
48069
+ };
48070
+ };
48071
+ var compareVoiceCostScenarios = (input) => {
48072
+ const baselineDef = input.scenarios.find((s) => s.id === input.baselineId);
48073
+ if (!baselineDef) {
48074
+ throw new Error(`Baseline scenario '${input.baselineId}' not found in scenarios list`);
48075
+ }
48076
+ const baseline = predictVoiceCallCost({
48077
+ priceBook: input.priceBook,
48078
+ profile: baselineDef.profile
48079
+ });
48080
+ return {
48081
+ baseline,
48082
+ scenarios: input.scenarios.map((scenario) => {
48083
+ const prediction = predictVoiceCallCost({
48084
+ priceBook: input.priceBook,
48085
+ profile: scenario.profile
48086
+ });
48087
+ return {
48088
+ delta: {
48089
+ monthlyUsd: round(prediction.monthly.totalUsd - baseline.monthly.totalUsd),
48090
+ perCallUsd: round(prediction.perCall.totalUsd - baseline.perCall.totalUsd)
48091
+ },
48092
+ prediction,
48093
+ scenarioId: scenario.id
48094
+ };
48095
+ })
48096
+ };
48097
+ };
48098
+ // src/iceServers.ts
48099
+ var toBase643 = (bytes) => {
48100
+ if (typeof Buffer !== "undefined") {
48101
+ return Buffer.from(bytes).toString("base64");
48102
+ }
48103
+ let bin = "";
48104
+ for (const byte of bytes)
48105
+ bin += String.fromCharCode(byte);
48106
+ return btoa(bin);
48107
+ };
48108
+ var defaultHmacSha1Base64 = async (key, message) => {
48109
+ const encoder2 = new TextEncoder;
48110
+ const cryptoKey = await crypto.subtle.importKey("raw", encoder2.encode(key), { hash: "SHA-1", name: "HMAC" }, false, ["sign"]);
48111
+ const signature = await crypto.subtle.sign("HMAC", cryptoKey, encoder2.encode(message));
48112
+ return toBase643(new Uint8Array(signature));
48113
+ };
48114
+ var createCoturnIceServers = async (input) => {
48115
+ const ttlSec = input.ttlSec ?? 3600;
48116
+ const now = input.now ?? Math.floor(Date.now() / 1000);
48117
+ const expires = now + ttlSec;
48118
+ const ephemeralUsername = `${expires}:${input.username}`;
48119
+ const credential = await (input.hmacSha1Base64 ?? defaultHmacSha1Base64)(input.sharedSecret, ephemeralUsername);
48120
+ const stun = input.stunUrls ?? ["stun:stun.l.google.com:19302"];
48121
+ return [
48122
+ { urls: stun },
48123
+ {
48124
+ credential,
48125
+ urls: [
48126
+ `turn:${input.turnHost}?transport=udp`,
48127
+ `turn:${input.turnHost}?transport=tcp`,
48128
+ `turns:${input.turnHost}?transport=tcp`
48129
+ ],
48130
+ username: ephemeralUsername
48131
+ }
48132
+ ];
48133
+ };
48134
+ var toBasicAuth2 = (sid, token) => `Basic ${btoa(`${sid}:${token}`)}`;
48135
+ var createTwilioNTSIceServers = async (input) => {
48136
+ if (!input.accountSid || !input.authToken) {
48137
+ throw new Error("Twilio NTS requires accountSid + authToken");
48138
+ }
48139
+ const fetchImpl = input.fetch ?? globalThis.fetch.bind(globalThis);
48140
+ const response = await fetchImpl(`https://api.twilio.com/2010-04-01/Accounts/${encodeURIComponent(input.accountSid)}/Tokens.json`, {
48141
+ headers: {
48142
+ accept: "application/json",
48143
+ authorization: toBasicAuth2(input.accountSid, input.authToken)
48144
+ },
48145
+ method: "POST"
48146
+ });
48147
+ if (!response.ok) {
48148
+ const text = await response.text().catch(() => "");
48149
+ throw new Error(`Twilio NTS Tokens failed: ${response.status} ${response.statusText} ${text.slice(0, 200)}`);
48150
+ }
48151
+ const payload = await response.json();
48152
+ const entries = payload.ice_servers ?? [];
48153
+ return entries.map((entry) => ({
48154
+ credential: entry.credential,
48155
+ urls: entry.urls ?? entry.url ?? [],
48156
+ username: entry.username
48157
+ }));
48158
+ };
48159
+ // src/holdAudio.ts
48160
+ var DEFAULT_CUES2 = [
48161
+ { text: "Let me look that up for you." },
48162
+ { text: "One moment while I check on that." },
48163
+ { text: "Still looking into this." }
48164
+ ];
48165
+ var createVoiceHoldAudioDriver = (options) => {
48166
+ const cues = options.cues ?? DEFAULT_CUES2;
48167
+ const cooldownMs = Math.max(0, options.cooldownMs ?? 4000);
48168
+ const thinkingThresholdMs = Math.max(0, options.thinkingThresholdMs ?? 1500);
48169
+ let thinkingSince;
48170
+ let lastCueAt;
48171
+ let cueIndex = 0;
48172
+ let timer;
48173
+ let firing = false;
48174
+ const tryFire = async (now) => {
48175
+ if (firing || cues.length === 0)
48176
+ return;
48177
+ if (thinkingSince === undefined)
48178
+ return;
48179
+ if (now - thinkingSince < thinkingThresholdMs)
48180
+ return;
48181
+ if (lastCueAt !== undefined && now - lastCueAt < cooldownMs)
48182
+ return;
48183
+ const cue = cues[cueIndex % cues.length];
48184
+ if (!cue)
48185
+ return;
48186
+ firing = true;
48187
+ try {
48188
+ await options.onCue(cue);
48189
+ } finally {
48190
+ firing = false;
48191
+ lastCueAt = now;
48192
+ cueIndex += 1;
48193
+ }
48194
+ };
48195
+ const scheduleNext = (now) => {
48196
+ if (thinkingSince === undefined)
48197
+ return;
48198
+ if (timer)
48199
+ clearTimeout(timer);
48200
+ const elapsed = now - thinkingSince;
48201
+ const delay = Math.max(0, lastCueAt === undefined ? thinkingThresholdMs - elapsed : cooldownMs - (now - lastCueAt));
48202
+ timer = setTimeout(() => {
48203
+ timer = undefined;
48204
+ const nextNow = Date.now();
48205
+ tryFire(nextNow).then(() => {
48206
+ if (thinkingSince !== undefined)
48207
+ scheduleNext(Date.now());
48208
+ });
48209
+ }, delay);
48210
+ };
48211
+ const clearTimer = () => {
48212
+ if (timer)
48213
+ clearTimeout(timer);
48214
+ timer = undefined;
48215
+ };
48216
+ return {
48217
+ noteResponse: () => {
48218
+ clearTimer();
48219
+ thinkingSince = undefined;
48220
+ },
48221
+ noteThinking: (timestampMs) => {
48222
+ const now = timestampMs ?? Date.now();
48223
+ if (thinkingSince === undefined) {
48224
+ thinkingSince = now;
48225
+ }
48226
+ scheduleNext(now);
48227
+ },
48228
+ reset: () => {
48229
+ clearTimer();
48230
+ thinkingSince = undefined;
48231
+ lastCueAt = undefined;
48232
+ cueIndex = 0;
48233
+ }
48234
+ };
48235
+ };
48236
+ // src/promptInjectionGuard.ts
48237
+ var DEFAULT_VOICE_PROMPT_INJECTION_RULES = [
48238
+ {
48239
+ label: "ignore-prior-instructions",
48240
+ pattern: /\bignore (?:all |the |any )?(?:previous|prior|above|earlier) (?:instructions|rules|prompts?)\b/iu,
48241
+ severity: "high"
48242
+ },
48243
+ {
48244
+ label: "role-override",
48245
+ pattern: /\byou are (?:now )?(?:a |an )?(?:different|new) (?:assistant|model|role)\b/iu,
48246
+ severity: "high"
48247
+ },
48248
+ {
48249
+ label: "system-prompt-leak",
48250
+ pattern: /\b(?:reveal|show|print|repeat|tell (?:me|us)|share) (?:your |the )?(?:system|hidden|secret) (?:prompt|instructions|message)\b/iu,
48251
+ severity: "high"
48252
+ },
48253
+ {
48254
+ label: "developer-impersonation",
48255
+ pattern: /\b(?:as your |i am the )?(?:developer|engineer|owner|admin)(?: of)?\b[^.]{0,40}\b(?:override|disable|bypass)/iu,
48256
+ severity: "medium"
48257
+ },
48258
+ {
48259
+ label: "jailbreak-persona",
48260
+ pattern: /\b(?:DAN|do anything now|jailbreak|developer mode|god mode)\b/iu,
48261
+ severity: "high"
48262
+ },
48263
+ {
48264
+ label: "tool-misuse-request",
48265
+ pattern: /\b(?:call|invoke|use) (?:the )?(?:transfer|hangup|end[_ ]call)(?:[_ ]?(?:tool|function))?\b/iu,
48266
+ severity: "low"
48267
+ }
48268
+ ];
48269
+ var extractText2 = (input) => typeof input === "string" ? input : input.text;
48270
+ var createVoicePromptInjectionGuard = (options = {}) => {
48271
+ const rules = options.rules ?? DEFAULT_VOICE_PROMPT_INJECTION_RULES;
48272
+ const replacement = options.sanitizedReplacement ?? "[REDACTED:INJECTION]";
48273
+ return {
48274
+ evaluate: (input) => {
48275
+ const text = extractText2(input);
48276
+ const matches = [];
48277
+ for (const rule of rules) {
48278
+ rule.pattern.lastIndex = 0;
48279
+ const match = rule.pattern.exec(text);
48280
+ if (match) {
48281
+ matches.push({
48282
+ label: rule.label,
48283
+ matchedText: match[0],
48284
+ severity: rule.severity
48285
+ });
48286
+ }
48287
+ }
48288
+ return { matches, ok: matches.length === 0 };
48289
+ },
48290
+ rules,
48291
+ sanitize: (text) => {
48292
+ let result = text;
48293
+ for (const rule of rules) {
48294
+ result = result.replace(new RegExp(rule.pattern, "gi"), replacement);
48295
+ }
48296
+ return result;
48297
+ }
48298
+ };
48299
+ };
48300
+ // src/postCallSurvey.ts
48301
+ var DEFAULT_VOICE_POST_CALL_SURVEY_QUESTIONS = [
48302
+ {
48303
+ id: "nps",
48304
+ max: 10,
48305
+ min: 0,
48306
+ prompt: "On a scale of zero to ten, how likely are you to recommend this service?",
48307
+ required: true,
48308
+ type: "rating"
48309
+ },
48310
+ {
48311
+ id: "resolved",
48312
+ prompt: "Did we resolve the reason for your call today?",
48313
+ required: true,
48314
+ type: "boolean"
48315
+ },
48316
+ {
48317
+ id: "comment",
48318
+ prompt: "Anything else you'd like to share before we wrap up?",
48319
+ type: "comment"
48320
+ }
48321
+ ];
48322
+ var bucketize = (rating) => {
48323
+ if (rating === null)
48324
+ return null;
48325
+ if (rating >= 9)
48326
+ return "promoter";
48327
+ if (rating >= 7)
48328
+ return "passive";
48329
+ return "detractor";
48330
+ };
48331
+ var validateRating = (question, value) => {
48332
+ if (typeof value !== "number" || Number.isNaN(value)) {
48333
+ throw new TypeError(`Question ${question.id} requires a numeric rating`);
48334
+ }
48335
+ const min = question.min ?? 0;
48336
+ const max = question.max ?? 10;
48337
+ if (value < min || value > max) {
48338
+ throw new RangeError(`Question ${question.id} expects a rating between ${min} and ${max}`);
48339
+ }
48340
+ return value;
48341
+ };
48342
+ var createVoicePostCallSurvey = (options) => {
48343
+ const now = options.now ?? (() => Date.now());
48344
+ const questions = options.questions ?? DEFAULT_VOICE_POST_CALL_SURVEY_QUESTIONS;
48345
+ const response = {
48346
+ answers: [],
48347
+ completedAt: null,
48348
+ npsBucket: null,
48349
+ sessionId: options.sessionId,
48350
+ startedAt: now()
48351
+ };
48352
+ const indexById = new Map(questions.map((q) => [q.id, q]));
48353
+ let cursor = 0;
48354
+ const next = () => cursor < questions.length ? questions[cursor] ?? null : null;
48355
+ const record = (questionId, value) => {
48356
+ const question = indexById.get(questionId);
48357
+ if (!question)
48358
+ throw new Error(`Unknown survey question: ${questionId}`);
48359
+ let stored = value;
48360
+ if (question.type === "rating")
48361
+ stored = validateRating(question, value);
48362
+ if (question.type === "boolean") {
48363
+ if (typeof value !== "boolean") {
48364
+ throw new TypeError(`Question ${questionId} requires a boolean answer`);
48365
+ }
48366
+ stored = value;
48367
+ }
48368
+ if (question.type === "comment" && value !== null && typeof value !== "string") {
48369
+ throw new TypeError(`Question ${questionId} requires a string answer`);
48370
+ }
48371
+ if (question.required && (stored === null || stored === "")) {
48372
+ throw new Error(`Question ${questionId} is required`);
48373
+ }
48374
+ const answer = { questionId, value: stored };
48375
+ response.answers.push(answer);
48376
+ if (questionId === "nps" && typeof stored === "number") {
48377
+ response.npsBucket = bucketize(stored);
48378
+ }
48379
+ const idx = questions.findIndex((q) => q.id === questionId);
48380
+ if (idx >= 0)
48381
+ cursor = Math.max(cursor, idx + 1);
48382
+ return answer;
48383
+ };
48384
+ const skip = () => {
48385
+ const question = next();
48386
+ if (!question)
48387
+ return null;
48388
+ if (question.required) {
48389
+ throw new Error(`Cannot skip required question: ${question.id}`);
48390
+ }
48391
+ response.answers.push({ questionId: question.id, value: null });
48392
+ cursor += 1;
48393
+ return question;
48394
+ };
48395
+ const complete = () => {
48396
+ for (const question of questions) {
48397
+ if (question.required && !response.answers.some((a) => a.questionId === question.id && a.value !== null)) {
48398
+ throw new Error(`Survey is missing required answer: ${question.id}`);
48399
+ }
48400
+ }
48401
+ response.completedAt = now();
48402
+ return response;
48403
+ };
48404
+ return {
48405
+ complete,
48406
+ getResponse: () => response,
48407
+ next,
48408
+ questions,
48409
+ record,
48410
+ skip
48411
+ };
48412
+ };
48413
+ var summarizeVoicePostCallSurveys = (responses) => {
48414
+ const completed = responses.filter((r) => r.completedAt !== null);
48415
+ const ratings = completed.flatMap((r) => r.answers).filter((a) => a.questionId === "nps" && typeof a.value === "number");
48416
+ const promoters = ratings.filter((a) => a.value >= 9).length;
48417
+ const detractors = ratings.filter((a) => a.value <= 6).length;
48418
+ const denom = ratings.length || 1;
48419
+ return {
48420
+ completion: responses.length === 0 ? 0 : completed.length / responses.length,
48421
+ detractors,
48422
+ nps: ratings.length === 0 ? null : (promoters - detractors) / denom * 100,
48423
+ promoters,
48424
+ sampleSize: ratings.length
48425
+ };
48426
+ };
48427
+ // src/dtmfCollector.ts
48428
+ var VOICE_DTMF_DIGITS = [
48429
+ "0",
48430
+ "1",
48431
+ "2",
48432
+ "3",
48433
+ "4",
48434
+ "5",
48435
+ "6",
48436
+ "7",
48437
+ "8",
48438
+ "9",
48439
+ "*",
48440
+ "#"
48441
+ ];
48442
+ var isDigit = (value) => VOICE_DTMF_DIGITS.includes(value);
48443
+ var collectVoiceDTMFInput = (options) => {
48444
+ const now = options.now ?? (() => Date.now());
48445
+ const minLength = options.minLength ?? 1;
48446
+ const maxLength = options.maxLength ?? minLength;
48447
+ if (maxLength < minLength) {
48448
+ throw new RangeError(`maxLength (${maxLength}) cannot be less than minLength (${minLength})`);
48449
+ }
48450
+ const terminator = options.terminator === undefined ? "#" : options.terminator;
48451
+ const timeoutMs = options.timeoutMs ?? 8000;
48452
+ const interDigitTimeoutMs = options.interDigitTimeoutMs ?? 3000;
48453
+ const startedAt = now();
48454
+ let lastDigitAt = startedAt;
48455
+ let state = { digits: "", status: "collecting" };
48456
+ const listeners = new Set;
48457
+ const notify = () => {
48458
+ for (const listener of listeners)
48459
+ listener(state);
48460
+ };
48461
+ const checkTimeouts = (at) => {
48462
+ if (state.status !== "collecting")
48463
+ return false;
48464
+ if (at - startedAt > timeoutMs) {
48465
+ state = { digits: state.digits, reason: "timeout", status: "rejected" };
48466
+ notify();
48467
+ return true;
48468
+ }
48469
+ if (state.digits.length > 0 && at - lastDigitAt > interDigitTimeoutMs) {
48470
+ state = { digits: state.digits, reason: "timeout", status: "rejected" };
48471
+ notify();
48472
+ return true;
48473
+ }
48474
+ return false;
48475
+ };
48476
+ const finish = (reason) => {
48477
+ if (state.status !== "collecting")
48478
+ return;
48479
+ const digits = state.digits;
48480
+ if (digits.length < minLength) {
48481
+ state = { digits, reason: "too-short", status: "rejected" };
48482
+ notify();
48483
+ return;
48484
+ }
48485
+ if (options.validator) {
48486
+ const verdict = options.validator(digits);
48487
+ if (verdict !== true) {
48488
+ state = { digits, reason: "invalid", status: "rejected" };
48489
+ notify();
48490
+ return;
48491
+ }
48492
+ }
48493
+ state = { digits, reason, status: "completed" };
48494
+ notify();
48495
+ };
48496
+ return {
48497
+ cancel() {
48498
+ if (state.status === "collecting") {
48499
+ state = { digits: state.digits, status: "cancelled" };
48500
+ notify();
48501
+ }
48502
+ return state;
48503
+ },
48504
+ feed(digit, at = now()) {
48505
+ if (state.status !== "collecting")
48506
+ return state;
48507
+ if (checkTimeouts(at))
48508
+ return state;
48509
+ if (!isDigit(digit)) {
48510
+ state = {
48511
+ digits: state.digits,
48512
+ reason: "invalid",
48513
+ status: "rejected"
48514
+ };
48515
+ notify();
48516
+ return state;
48517
+ }
48518
+ lastDigitAt = at;
48519
+ if (terminator && digit === terminator) {
48520
+ finish("terminator");
48521
+ return state;
48522
+ }
48523
+ const digits = state.digits + digit;
48524
+ state = { digits, status: "collecting" };
48525
+ if (digits.length >= maxLength) {
48526
+ finish("length");
48527
+ } else {
48528
+ notify();
48529
+ }
48530
+ return state;
48531
+ },
48532
+ getState: () => state,
48533
+ prompt: options.prompt,
48534
+ subscribe(listener) {
48535
+ listeners.add(listener);
48536
+ listener(state);
48537
+ return () => {
48538
+ listeners.delete(listener);
48539
+ };
48540
+ },
48541
+ tick(at = now()) {
48542
+ checkTimeouts(at);
48543
+ return state;
48544
+ }
48545
+ };
48546
+ };
48547
+ var validateVoiceDTMFLuhn = (digits) => {
48548
+ if (!/^\d+$/u.test(digits))
48549
+ return false;
48550
+ let sum = 0;
48551
+ let alt = false;
48552
+ for (let i = digits.length - 1;i >= 0; i--) {
48553
+ let n = Number(digits[i]);
48554
+ if (alt) {
48555
+ n *= 2;
48556
+ if (n > 9)
48557
+ n -= 9;
48558
+ }
48559
+ sum += n;
48560
+ alt = !alt;
48561
+ }
48562
+ return sum % 10 === 0;
48563
+ };
48014
48564
  export {
48015
48565
  writeVoiceProofPack,
48016
48566
  writeVoiceMediaPipelineArtifacts,
@@ -48032,6 +48582,7 @@ export {
48032
48582
  verifyVoiceOpsWebhookSignature,
48033
48583
  validateVoiceWorkflowRouteResult,
48034
48584
  validateVoiceObservabilityExportRecord,
48585
+ validateVoiceDTMFLuhn,
48035
48586
  ttsAdapterSessionCanCancel,
48036
48587
  transcodeTwilioInboundPayloadToPCM16,
48037
48588
  transcodePCMToTwilioOutboundPayload,
@@ -48052,6 +48603,7 @@ export {
48052
48603
  summarizeVoiceProviderCapabilities,
48053
48604
  summarizeVoiceProofAssertions,
48054
48605
  summarizeVoiceProductionReadinessGate,
48606
+ summarizeVoicePostCallSurveys,
48055
48607
  summarizeVoiceOpsTasks,
48056
48608
  summarizeVoiceOpsTaskQueue,
48057
48609
  summarizeVoiceOpsTaskAnalytics,
@@ -48254,6 +48806,7 @@ export {
48254
48806
  pruneVoiceIncidentBundleArtifacts,
48255
48807
  provisionTwilioPhoneNumber,
48256
48808
  provisionTelnyxPhoneNumber,
48809
+ predictVoiceCallCost,
48257
48810
  parseVoiceTelephonyWebhookEvent,
48258
48811
  parseVoiceSessionSnapshot,
48259
48812
  normalizeVoiceProofTrendReport,
@@ -48501,6 +49054,7 @@ export {
48501
49054
  createVoiceProofPackBuildContext,
48502
49055
  createVoiceProofPackArtifacts,
48503
49056
  createVoiceProofAssertion,
49057
+ createVoicePromptInjectionGuard,
48504
49058
  createVoiceProfileTraceTagger,
48505
49059
  createVoiceProfileSwitchReadinessRoutes,
48506
49060
  createVoiceProfileSwitchPolicyProofRoutes,
@@ -48522,6 +49076,7 @@ export {
48522
49076
  createVoicePostgresCampaignStore,
48523
49077
  createVoicePostgresAuditSinkDeliveryStore,
48524
49078
  createVoicePostgresAuditEventStore,
49079
+ createVoicePostCallSurvey,
48525
49080
  createVoicePostCallAnalysisRoutes,
48526
49081
  createVoicePlivoWebhookVerifier,
48527
49082
  createVoicePlivoCampaignDialer,
@@ -48593,6 +49148,7 @@ export {
48593
49148
  createVoiceHubSpotTaskUpdateSink,
48594
49149
  createVoiceHubSpotTaskSyncSinks,
48595
49150
  createVoiceHubSpotTaskSink,
49151
+ createVoiceHoldAudioDriver,
48596
49152
  createVoiceHelpdeskTicketSink,
48597
49153
  createVoiceHandoffHealthRoutes,
48598
49154
  createVoiceHandoffHealthJSONHandler,
@@ -48688,6 +49244,7 @@ export {
48688
49244
  createVoiceAIJudgeCompletion,
48689
49245
  createTwilioVoiceRoutes,
48690
49246
  createTwilioVoiceResponse,
49247
+ createTwilioNTSIceServers,
48691
49248
  createTwilioMediaStreamBridge,
48692
49249
  createTelnyxVoiceRoutes,
48693
49250
  createTelnyxVoiceResponse,
@@ -48719,12 +49276,15 @@ export {
48719
49276
  createGeminiVoiceAssistantModel,
48720
49277
  createDomainPhraseHints,
48721
49278
  createDomainLexicon,
49279
+ createCoturnIceServers,
48722
49280
  createAnthropicVoiceAssistantModel,
48723
49281
  createAIVoiceModel,
48724
49282
  conditionAudioChunk,
48725
49283
  computePcmDurationMs,
48726
49284
  completeVoiceOpsTask,
48727
49285
  compareVoiceEvalBaseline,
49286
+ compareVoiceCostScenarios,
49287
+ collectVoiceDTMFInput,
48728
49288
  claimVoiceOpsTask,
48729
49289
  buildVoiceTraceReplay,
48730
49290
  buildVoiceTraceDeliveryReport,
@@ -48876,11 +49436,14 @@ export {
48876
49436
  VOICE_WEBHOOK_TIMESTAMP_HEADER,
48877
49437
  VOICE_WEBHOOK_SIGNATURE_HEADER,
48878
49438
  VOICE_LIVE_OPS_ACTIONS,
49439
+ VOICE_DTMF_DIGITS,
48879
49440
  VOICE_CALLER_MEMORY_KEY,
48880
49441
  TURN_PROFILE_DEFAULTS,
48881
49442
  DEFAULT_VOICE_REDACTION_PATTERNS,
48882
49443
  DEFAULT_VOICE_PROOF_TREND_PROFILE_DEFINITIONS,
48883
49444
  DEFAULT_VOICE_PROOF_TRENDS_MAX_AGE_MS,
49445
+ DEFAULT_VOICE_PROMPT_INJECTION_RULES,
48884
49446
  DEFAULT_VOICE_PRICE_BOOK,
49447
+ DEFAULT_VOICE_POST_CALL_SURVEY_QUESTIONS,
48885
49448
  BROWSER_NOISE_SUPPRESSOR_PRESETS
48886
49449
  };
@@ -0,0 +1,41 @@
1
+ export type VoicePostCallSurveyQuestion = {
2
+ id: string;
3
+ prompt: string;
4
+ type: "rating" | "boolean" | "comment";
5
+ min?: number;
6
+ max?: number;
7
+ required?: boolean;
8
+ };
9
+ export type VoicePostCallSurveyAnswer = {
10
+ questionId: string;
11
+ value: number | boolean | string | null;
12
+ };
13
+ export type VoicePostCallSurveyResponse = {
14
+ sessionId: string;
15
+ startedAt: number;
16
+ completedAt: number | null;
17
+ answers: VoicePostCallSurveyAnswer[];
18
+ npsBucket: "promoter" | "passive" | "detractor" | null;
19
+ };
20
+ export type CreateVoicePostCallSurveyOptions = {
21
+ sessionId: string;
22
+ questions?: VoicePostCallSurveyQuestion[];
23
+ now?: () => number;
24
+ };
25
+ export declare const DEFAULT_VOICE_POST_CALL_SURVEY_QUESTIONS: VoicePostCallSurveyQuestion[];
26
+ export declare const createVoicePostCallSurvey: (options: CreateVoicePostCallSurveyOptions) => {
27
+ complete: () => VoicePostCallSurveyResponse;
28
+ getResponse: () => VoicePostCallSurveyResponse;
29
+ next: () => VoicePostCallSurveyQuestion | null;
30
+ questions: VoicePostCallSurveyQuestion[];
31
+ record: (questionId: string, value: number | boolean | string | null) => VoicePostCallSurveyAnswer;
32
+ skip: () => VoicePostCallSurveyQuestion | null;
33
+ };
34
+ export type VoicePostCallSurvey = ReturnType<typeof createVoicePostCallSurvey>;
35
+ export declare const summarizeVoicePostCallSurveys: (responses: VoicePostCallSurveyResponse[]) => {
36
+ completion: number;
37
+ detractors: number;
38
+ nps: number | null;
39
+ promoters: number;
40
+ sampleSize: number;
41
+ };
@@ -0,0 +1,30 @@
1
+ import type { Transcript } from "./types";
2
+ export type VoicePromptInjectionRule = {
3
+ /** Reason emitted in the verdict when this rule matches. */
4
+ label: string;
5
+ /** Regex applied to the transcript text (case-insensitive by convention). */
6
+ pattern: RegExp;
7
+ /** Severity tag for downstream routing. */
8
+ severity?: "high" | "low" | "medium";
9
+ };
10
+ export type VoicePromptInjectionVerdict = {
11
+ matches: Array<{
12
+ label: string;
13
+ matchedText: string;
14
+ severity?: VoicePromptInjectionRule["severity"];
15
+ }>;
16
+ ok: boolean;
17
+ };
18
+ export declare const DEFAULT_VOICE_PROMPT_INJECTION_RULES: VoicePromptInjectionRule[];
19
+ export type CreateVoicePromptInjectionGuardOptions = {
20
+ /** Custom rule set. Defaults to DEFAULT_VOICE_PROMPT_INJECTION_RULES. */
21
+ rules?: ReadonlyArray<VoicePromptInjectionRule>;
22
+ /** Replacement string for sanitized text. Default '[REDACTED:INJECTION]'. */
23
+ sanitizedReplacement?: string;
24
+ };
25
+ export type VoicePromptInjectionGuard = {
26
+ evaluate: (input: string | Transcript) => VoicePromptInjectionVerdict;
27
+ rules: ReadonlyArray<VoicePromptInjectionRule>;
28
+ sanitize: (input: string) => string;
29
+ };
30
+ export declare const createVoicePromptInjectionGuard: (options?: CreateVoicePromptInjectionGuardOptions) => VoicePromptInjectionGuard;