@absolutejs/voice 0.0.22-beta.511 → 0.0.22-beta.513

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
@@ -10,6 +10,19 @@ var __name = (target, name) => {
10
10
  });
11
11
  return target;
12
12
  };
13
+ var __returnValue = (v) => v;
14
+ function __exportSetter(name, newValue) {
15
+ this[name] = __returnValue.bind(null, newValue);
16
+ }
17
+ var __export = (target, all) => {
18
+ for (var name in all)
19
+ __defProp(target, name, {
20
+ get: all[name],
21
+ enumerable: true,
22
+ configurable: true,
23
+ set: __exportSetter.bind(all, name)
24
+ });
25
+ };
13
26
  var __knownSymbol = (name, symbol) => (symbol = Symbol[name]) ? symbol : Symbol.for("Symbol." + name);
14
27
  var __typeError = (msg) => {
15
28
  throw TypeError(msg);
@@ -70,6 +83,115 @@ var __decorateElement = (array, flags, name, decorators, target, extra) => {
70
83
  };
71
84
  var __require = import.meta.require;
72
85
 
86
+ // src/calendarSlots.ts
87
+ var exports_calendarSlots = {};
88
+ __export(exports_calendarSlots, {
89
+ summarizeVoiceCalendarSlot: () => summarizeVoiceCalendarSlot,
90
+ generateVoiceCalendarSlots: () => generateVoiceCalendarSlots
91
+ });
92
+ var parseHHMM = (value) => {
93
+ const match = /^([0-9]{1,2}):([0-9]{2})$/u.exec(value);
94
+ if (!match)
95
+ throw new Error(`Invalid time string (expected HH:MM): ${value}`);
96
+ return Number(match[1]) * 60 + Number(match[2]);
97
+ }, partsAt = (ms, timezone) => {
98
+ const formatter = new Intl.DateTimeFormat("en-US", {
99
+ day: "2-digit",
100
+ hour: "2-digit",
101
+ hour12: false,
102
+ minute: "2-digit",
103
+ month: "2-digit",
104
+ timeZone: timezone,
105
+ weekday: "short",
106
+ year: "numeric"
107
+ });
108
+ const map = {};
109
+ for (const part of formatter.formatToParts(new Date(ms))) {
110
+ if (part.type !== "literal")
111
+ map[part.type] = part.value;
112
+ }
113
+ const weekdayMap = {
114
+ Fri: 5,
115
+ Mon: 1,
116
+ Sat: 6,
117
+ Sun: 0,
118
+ Thu: 4,
119
+ Tue: 2,
120
+ Wed: 3
121
+ };
122
+ const hourValue = map.hour === "24" ? "00" : map.hour ?? "0";
123
+ return {
124
+ date: `${map.year}-${map.month}-${map.day}`,
125
+ minutes: Number(hourValue) * 60 + Number(map.minute ?? "0"),
126
+ weekday: weekdayMap[map.weekday ?? ""] ?? 0
127
+ };
128
+ }, overlaps = (aStart, aEnd, bStart, bEnd) => aStart < bEnd && bStart < aEnd, generateVoiceCalendarSlots = (input) => {
129
+ if (input.durationMinutes <= 0) {
130
+ throw new Error("durationMinutes must be positive");
131
+ }
132
+ if (input.toMs <= input.fromMs)
133
+ return [];
134
+ const granularity = input.granularityMinutes ?? 15;
135
+ const buffer = input.bufferMinutes ?? 0;
136
+ const max = input.maxSlots ?? Infinity;
137
+ const hoursByDay = new Map;
138
+ for (const block of input.businessHours) {
139
+ const list = hoursByDay.get(block.weekday) ?? [];
140
+ list.push(block);
141
+ hoursByDay.set(block.weekday, list);
142
+ }
143
+ const blackoutDates = new Set((input.blackoutDates ?? []).map((b) => b.date));
144
+ const slots = [];
145
+ const stepMs = granularity * 60000;
146
+ const durationMs = input.durationMinutes * 60000;
147
+ const bufferMs = buffer * 60000;
148
+ let cursor = input.fromMs;
149
+ while (cursor + durationMs <= input.toMs && slots.length < max) {
150
+ const slotEnd = cursor + durationMs;
151
+ const startParts = partsAt(cursor, input.timezone);
152
+ if (blackoutDates.has(startParts.date)) {
153
+ cursor += stepMs;
154
+ continue;
155
+ }
156
+ const dayHours = hoursByDay.get(startParts.weekday);
157
+ if (!dayHours || dayHours.length === 0) {
158
+ cursor += stepMs;
159
+ continue;
160
+ }
161
+ const endParts = partsAt(slotEnd - 1, input.timezone);
162
+ const fitsHours = dayHours.some((block) => {
163
+ const startMin = parseHHMM(block.start);
164
+ const endMin = parseHHMM(block.end);
165
+ return startParts.minutes >= startMin && endParts.minutes < endMin && startParts.date === endParts.date;
166
+ });
167
+ if (!fitsHours) {
168
+ cursor += stepMs;
169
+ continue;
170
+ }
171
+ const collides = (input.bookedRanges ?? []).some((booked) => overlaps(cursor - bufferMs, slotEnd + bufferMs, booked.startMs, booked.endMs));
172
+ if (!collides) {
173
+ slots.push({
174
+ durationMinutes: input.durationMinutes,
175
+ endMs: slotEnd,
176
+ startMs: cursor
177
+ });
178
+ }
179
+ cursor += stepMs;
180
+ }
181
+ return slots;
182
+ }, summarizeVoiceCalendarSlot = (slot, options = {}) => {
183
+ const formatter = new Intl.DateTimeFormat(options.locale ?? "en-US", {
184
+ day: "numeric",
185
+ hour: "numeric",
186
+ hour12: true,
187
+ minute: "2-digit",
188
+ month: "long",
189
+ timeZone: options.timezone,
190
+ weekday: "long"
191
+ });
192
+ return formatter.format(new Date(slot.startMs));
193
+ };
194
+
73
195
  // src/audioConditioning.ts
74
196
  var DEFAULT_TARGET_LEVEL = 0.08;
75
197
  var DEFAULT_MAX_GAIN = 3;
@@ -49602,6 +49724,871 @@ var createVoiceSupervisorPermissions = (options = {}) => {
49602
49724
  };
49603
49725
  };
49604
49726
  var VOICE_SUPERVISOR_TIER_CAPABILITIES = TIER_CAPABILITIES;
49727
+ // src/calendarAdapter.ts
49728
+ var createVoiceInMemoryCalendarAdapter = (options) => {
49729
+ const now = options.now ?? (() => Date.now());
49730
+ const generateId = options.generateId ?? (() => `appt_${Math.random().toString(36).slice(2, 10)}`);
49731
+ const appointments = new Map;
49732
+ for (const range of options.bookedRanges ?? []) {
49733
+ const id = generateId();
49734
+ appointments.set(id, {
49735
+ calendarId: "default",
49736
+ createdAt: now(),
49737
+ endMs: range.endMs,
49738
+ id,
49739
+ startMs: range.startMs,
49740
+ status: "scheduled"
49741
+ });
49742
+ }
49743
+ const liveRanges = () => Array.from(appointments.values()).filter((a) => a.status === "scheduled").map((a) => ({ endMs: a.endMs, startMs: a.startMs }));
49744
+ return {
49745
+ async book(input) {
49746
+ const clash = liveRanges().some((r) => input.startMs < r.endMs && r.startMs < input.endMs);
49747
+ if (clash)
49748
+ throw new Error("Slot is already booked");
49749
+ const id = generateId();
49750
+ const appointment = {
49751
+ calendarId: input.calendarId,
49752
+ createdAt: now(),
49753
+ endMs: input.endMs,
49754
+ id,
49755
+ startMs: input.startMs,
49756
+ status: "scheduled",
49757
+ ...input.title !== undefined ? { title: input.title } : {},
49758
+ ...input.attendees !== undefined ? { attendees: input.attendees } : {},
49759
+ ...input.notes !== undefined ? { notes: input.notes } : {},
49760
+ ...input.metadata !== undefined ? { metadata: input.metadata } : {}
49761
+ };
49762
+ appointments.set(id, appointment);
49763
+ return appointment;
49764
+ },
49765
+ async cancel(id) {
49766
+ const existing = appointments.get(id);
49767
+ if (!existing)
49768
+ return null;
49769
+ const cancelled = {
49770
+ ...existing,
49771
+ status: "cancelled"
49772
+ };
49773
+ appointments.set(id, cancelled);
49774
+ return cancelled;
49775
+ },
49776
+ async get(id) {
49777
+ return appointments.get(id) ?? null;
49778
+ },
49779
+ async listAvailability(query) {
49780
+ const { generateVoiceCalendarSlots: generateVoiceCalendarSlots2 } = await Promise.resolve().then(() => exports_calendarSlots);
49781
+ return generateVoiceCalendarSlots2({
49782
+ bookedRanges: liveRanges(),
49783
+ ...query.bufferMinutes !== undefined ? { bufferMinutes: query.bufferMinutes } : {},
49784
+ businessHours: options.businessHours,
49785
+ durationMinutes: query.durationMinutes,
49786
+ fromMs: query.fromMs,
49787
+ ...query.granularityMinutes !== undefined ? { granularityMinutes: query.granularityMinutes } : {},
49788
+ ...query.maxSlots !== undefined ? { maxSlots: query.maxSlots } : {},
49789
+ ...options.timezone !== undefined ? { timezone: options.timezone } : {},
49790
+ toMs: query.toMs
49791
+ });
49792
+ },
49793
+ providerName: "in-memory",
49794
+ async reschedule(id, nextStartMs, nextEndMs) {
49795
+ const existing = appointments.get(id);
49796
+ if (!existing)
49797
+ return null;
49798
+ const others = liveRanges().filter((r) => r.startMs !== existing.startMs || r.endMs !== existing.endMs);
49799
+ const clash = others.some((r) => nextStartMs < r.endMs && r.startMs < nextEndMs);
49800
+ if (clash)
49801
+ throw new Error("Cannot reschedule onto a booked slot");
49802
+ const updated = {
49803
+ ...existing,
49804
+ endMs: nextEndMs,
49805
+ startMs: nextStartMs
49806
+ };
49807
+ appointments.set(id, updated);
49808
+ return updated;
49809
+ }
49810
+ };
49811
+ };
49812
+ // src/bookingFlow.ts
49813
+ var createVoiceBookingFlow = (options) => {
49814
+ const initial = {
49815
+ proposedSlots: [],
49816
+ step: options.initialStep ?? (options.services ? "ask-service" : "ask-date")
49817
+ };
49818
+ let state = initial;
49819
+ const listeners = new Set;
49820
+ const setState = (next) => {
49821
+ state = { ...state, ...next };
49822
+ for (const listener of listeners)
49823
+ listener(state);
49824
+ };
49825
+ const chooseService = (serviceId) => {
49826
+ const services = options.services ?? [];
49827
+ const service = services.find((s) => s.id === serviceId);
49828
+ if (!service) {
49829
+ setState({ error: `Unknown service: ${serviceId}`, step: "failed" });
49830
+ return;
49831
+ }
49832
+ setState({
49833
+ serviceDurationMinutes: service.durationMinutes,
49834
+ serviceId: service.id,
49835
+ step: "ask-date"
49836
+ });
49837
+ };
49838
+ const proposeSlotsForDay = async (input) => {
49839
+ const duration = state.serviceDurationMinutes ?? options.defaultDurationMinutes ?? 30;
49840
+ const slots = await options.adapter.listAvailability({
49841
+ calendarId: options.calendarId,
49842
+ durationMinutes: duration,
49843
+ fromMs: input.fromMs,
49844
+ ...options.maxSlotsPerDay !== undefined ? { maxSlots: options.maxSlotsPerDay } : {},
49845
+ toMs: input.toMs
49846
+ });
49847
+ setState({ proposedSlots: slots, step: slots.length > 0 ? "ask-time" : "ask-date" });
49848
+ return slots;
49849
+ };
49850
+ const chooseSlot = (slotIndex) => {
49851
+ const slot = state.proposedSlots[slotIndex];
49852
+ if (!slot) {
49853
+ setState({ error: "Invalid slot selection", step: "failed" });
49854
+ return;
49855
+ }
49856
+ setState({ selectedSlot: slot, step: "confirm" });
49857
+ };
49858
+ const confirm = async (input = {}) => {
49859
+ if (state.step !== "confirm" || !state.selectedSlot) {
49860
+ setState({ error: "Nothing to confirm", step: "failed" });
49861
+ return null;
49862
+ }
49863
+ setState({ step: "booking" });
49864
+ try {
49865
+ const appt = await options.adapter.book({
49866
+ calendarId: options.calendarId,
49867
+ endMs: state.selectedSlot.endMs,
49868
+ startMs: state.selectedSlot.startMs,
49869
+ ...input.attendees !== undefined ? { attendees: input.attendees } : {},
49870
+ ...input.title !== undefined ? { title: input.title } : {},
49871
+ ...input.notes !== undefined ? { notes: input.notes } : {}
49872
+ });
49873
+ setState({ appointment: appt, step: "booked" });
49874
+ return appt;
49875
+ } catch (error) {
49876
+ setState({
49877
+ error: error instanceof Error ? error.message : String(error),
49878
+ step: "failed"
49879
+ });
49880
+ return null;
49881
+ }
49882
+ };
49883
+ const reset = () => {
49884
+ state = {
49885
+ proposedSlots: [],
49886
+ step: options.initialStep ?? (options.services ? "ask-service" : "ask-date")
49887
+ };
49888
+ for (const listener of listeners)
49889
+ listener(state);
49890
+ };
49891
+ return {
49892
+ chooseService,
49893
+ chooseSlot,
49894
+ confirm,
49895
+ getState: () => state,
49896
+ proposeSlotsForDay,
49897
+ reset,
49898
+ subscribe(listener) {
49899
+ listeners.add(listener);
49900
+ listener(state);
49901
+ return () => {
49902
+ listeners.delete(listener);
49903
+ };
49904
+ }
49905
+ };
49906
+ };
49907
+ // src/noShowPredictor.ts
49908
+ var clamp = (value, min = 0, max = 1) => Math.max(min, Math.min(max, value));
49909
+ var scoreVoiceNoShowRisk = (input) => {
49910
+ const now = input.now ?? (() => Date.now());
49911
+ const leadHours = (input.appointmentStartMs - input.bookedAtMs) / 3600000;
49912
+ const startDate = new Date(input.appointmentStartMs);
49913
+ const weekday = startDate.getUTCDay();
49914
+ const hour = startDate.getUTCHours();
49915
+ const history = input.history ?? [];
49916
+ const past = history.filter((r) => r.scheduledStartMs < input.appointmentStartMs);
49917
+ const priorNoShows = past.filter((r) => r.outcome === "no-show").length;
49918
+ const priorKept = past.filter((r) => r.outcome === "kept").length;
49919
+ let score = 0.15;
49920
+ const drivers = [];
49921
+ if (leadHours > 72) {
49922
+ score += 0.1;
49923
+ drivers.push({ kind: "lead-time-hours", value: leadHours });
49924
+ } else if (leadHours < 12) {
49925
+ score -= 0.05;
49926
+ drivers.push({ kind: "lead-time-hours", value: leadHours });
49927
+ }
49928
+ if (weekday === 1) {
49929
+ score += 0.04;
49930
+ drivers.push({ kind: "weekday", value: weekday });
49931
+ }
49932
+ if (weekday === 5) {
49933
+ score += 0.03;
49934
+ drivers.push({ kind: "weekday", value: weekday });
49935
+ }
49936
+ if (hour < 9 || hour >= 17) {
49937
+ score += 0.04;
49938
+ drivers.push({ kind: "hour-of-day", value: hour });
49939
+ }
49940
+ if (priorNoShows > 0) {
49941
+ const delta = Math.min(0.5, priorNoShows * 0.2);
49942
+ score += delta;
49943
+ drivers.push({ kind: "prior-no-show-count", value: priorNoShows });
49944
+ }
49945
+ if (priorKept > 2 && priorNoShows === 0) {
49946
+ score -= 0.08;
49947
+ drivers.push({ kind: "prior-kept-count", value: priorKept });
49948
+ }
49949
+ if (input.reminderConfirmed === true) {
49950
+ score -= 0.15;
49951
+ drivers.push({ kind: "reminder-confirmed", value: true });
49952
+ } else if (input.reminderConfirmed === false) {
49953
+ score += 0.1;
49954
+ drivers.push({ kind: "reminder-confirmed", value: false });
49955
+ }
49956
+ if (input.callbackDistanceHours !== undefined && input.callbackDistanceHours > 24) {
49957
+ score += 0.06;
49958
+ drivers.push({
49959
+ kind: "callback-distance-hours",
49960
+ value: input.callbackDistanceHours
49961
+ });
49962
+ }
49963
+ if (input.weatherDisruption) {
49964
+ score += 0.18;
49965
+ drivers.push({ kind: "weather-disruption", value: true });
49966
+ }
49967
+ const finalScore = clamp(score);
49968
+ const band = finalScore >= 0.55 ? "high" : finalScore >= 0.3 ? "moderate" : "low";
49969
+ return { band, drivers, score: finalScore };
49970
+ };
49971
+ var summarizeVoiceNoShowVerdict = (verdict) => {
49972
+ const pct = Math.round(verdict.score * 100);
49973
+ const top = verdict.drivers.slice(0, 2).map((d) => d.kind).join(", ");
49974
+ return `${verdict.band} risk (${pct}%)${top ? ` \u2014 driven by ${top}` : ""}`;
49975
+ };
49976
+ // src/reminderScheduler.ts
49977
+ var DEFAULT_VOICE_REMINDER_TRIGGERS = [
49978
+ {
49979
+ channel: "sms",
49980
+ id: "remind-24h",
49981
+ offsetMinutesBeforeStart: 24 * 60
49982
+ },
49983
+ {
49984
+ channel: "sms",
49985
+ id: "remind-2h",
49986
+ offsetMinutesBeforeStart: 120
49987
+ },
49988
+ {
49989
+ channel: "call",
49990
+ id: "remind-call-30m",
49991
+ offsetMinutesBeforeStart: 30,
49992
+ retryOnFailure: true
49993
+ }
49994
+ ];
49995
+ var createVoiceReminderScheduler = (options = {}) => {
49996
+ const now = options.now ?? (() => Date.now());
49997
+ const generateId = options.generateJobId ?? (() => `rem_${Math.random().toString(36).slice(2, 10)}`);
49998
+ const defaultTriggers = options.defaultTriggers ?? DEFAULT_VOICE_REMINDER_TRIGGERS;
49999
+ const maxAttempts = options.maxAttempts ?? 2;
50000
+ const jobs = new Map;
50001
+ const listeners = new Set;
50002
+ const broadcast = (job) => {
50003
+ for (const listener of listeners)
50004
+ listener(job);
50005
+ };
50006
+ const schedule = (input) => {
50007
+ const triggers = input.triggers.length > 0 ? input.triggers : defaultTriggers;
50008
+ const at = now();
50009
+ const created = [];
50010
+ for (const trigger of triggers) {
50011
+ const fireAt = input.appointmentStartMs - trigger.offsetMinutesBeforeStart * 60000;
50012
+ if (fireAt <= at)
50013
+ continue;
50014
+ const job = {
50015
+ appointmentId: input.appointmentId,
50016
+ attempts: 0,
50017
+ channel: trigger.channel,
50018
+ id: generateId(),
50019
+ scheduledAtMs: fireAt,
50020
+ status: "pending",
50021
+ triggerId: trigger.id,
50022
+ ...input.metadata !== undefined ? { metadata: input.metadata } : {}
50023
+ };
50024
+ jobs.set(job.id, job);
50025
+ created.push(job);
50026
+ broadcast(job);
50027
+ }
50028
+ return created;
50029
+ };
50030
+ const due = (at = now()) => Array.from(jobs.values()).filter((j) => j.status === "pending" && j.scheduledAtMs <= at);
50031
+ const markInFlight = (jobId) => {
50032
+ const job = jobs.get(jobId);
50033
+ if (!job || job.status !== "pending")
50034
+ return false;
50035
+ job.status = "in-flight";
50036
+ job.attempts += 1;
50037
+ broadcast(job);
50038
+ return true;
50039
+ };
50040
+ const markSent = (jobId) => {
50041
+ const job = jobs.get(jobId);
50042
+ if (!job)
50043
+ return false;
50044
+ job.status = "sent";
50045
+ broadcast(job);
50046
+ return true;
50047
+ };
50048
+ const markFailed = (jobId, error) => {
50049
+ const job = jobs.get(jobId);
50050
+ if (!job)
50051
+ return false;
50052
+ job.lastError = error;
50053
+ if (job.attempts < maxAttempts) {
50054
+ job.status = "pending";
50055
+ job.scheduledAtMs = now() + 5 * 60000;
50056
+ } else {
50057
+ job.status = "failed";
50058
+ }
50059
+ broadcast(job);
50060
+ return true;
50061
+ };
50062
+ const cancelForAppointment = (appointmentId) => {
50063
+ let count = 0;
50064
+ for (const job of jobs.values()) {
50065
+ if (job.appointmentId === appointmentId && (job.status === "pending" || job.status === "in-flight")) {
50066
+ job.status = "cancelled";
50067
+ broadcast(job);
50068
+ count += 1;
50069
+ }
50070
+ }
50071
+ return count;
50072
+ };
50073
+ return {
50074
+ cancelForAppointment,
50075
+ due,
50076
+ list: (appointmentId) => Array.from(jobs.values()).filter((j) => !appointmentId || j.appointmentId === appointmentId),
50077
+ markFailed,
50078
+ markInFlight,
50079
+ markSent,
50080
+ schedule,
50081
+ subscribe(listener) {
50082
+ listeners.add(listener);
50083
+ return () => {
50084
+ listeners.delete(listener);
50085
+ };
50086
+ }
50087
+ };
50088
+ };
50089
+ // src/callScorecard.ts
50090
+ var clampScore = (raw, max) => Math.max(0, Math.min(max, raw));
50091
+ var buildVoiceCallScorecard = (input) => {
50092
+ const now = input.now ?? (() => Date.now());
50093
+ const scaleMax = input.rubric.scaleMax ?? 5;
50094
+ const passingGrade = input.rubric.passingGrade ?? 0.7;
50095
+ const totalWeight = input.rubric.criteria.reduce((sum, c) => sum + c.weight, 0);
50096
+ if (totalWeight <= 0) {
50097
+ throw new Error("Rubric weights must sum to a positive number");
50098
+ }
50099
+ const results = [];
50100
+ const failedRequiredCriteria = [];
50101
+ const sectionAccum = new Map;
50102
+ for (const criterion of input.rubric.criteria) {
50103
+ const raw = input.scores[criterion.id];
50104
+ if (!raw) {
50105
+ throw new Error(`Missing score for criterion: ${criterion.id}`);
50106
+ }
50107
+ const score = clampScore(raw.score, scaleMax);
50108
+ const passingScore = criterion.passingScore ?? scaleMax * 0.6;
50109
+ const passed = score >= passingScore;
50110
+ const result = {
50111
+ criterionId: criterion.id,
50112
+ passed,
50113
+ score,
50114
+ weight: criterion.weight,
50115
+ ...raw.rationale !== undefined ? { rationale: raw.rationale } : {}
50116
+ };
50117
+ results.push(result);
50118
+ if (!passed && criterion.required) {
50119
+ failedRequiredCriteria.push(criterion.id);
50120
+ }
50121
+ const section = criterion.section ?? "default";
50122
+ const entry = sectionAccum.get(section) ?? { weight: 0, weighted: 0 };
50123
+ entry.weighted += score * criterion.weight;
50124
+ entry.weight += criterion.weight;
50125
+ sectionAccum.set(section, entry);
50126
+ }
50127
+ const weightedSum = results.reduce((sum, r) => sum + r.score * r.weight, 0);
50128
+ const weightedScore = weightedSum / (totalWeight * scaleMax);
50129
+ const sectionScores = {};
50130
+ for (const [section, accum] of sectionAccum) {
50131
+ sectionScores[section] = accum.weight === 0 ? 0 : accum.weighted / (accum.weight * scaleMax);
50132
+ }
50133
+ const grade = failedRequiredCriteria.length > 0 ? "fail" : weightedScore >= passingGrade ? "pass" : "needs-review";
50134
+ return {
50135
+ createdAt: now(),
50136
+ failedRequiredCriteria,
50137
+ grade,
50138
+ passingGrade,
50139
+ results,
50140
+ reviewer: input.reviewer,
50141
+ rubricId: input.rubric.id,
50142
+ scaleMax,
50143
+ sectionScores,
50144
+ sessionId: input.sessionId,
50145
+ weightedScore,
50146
+ ...input.agentId !== undefined ? { agentId: input.agentId } : {},
50147
+ ...input.reviewerId !== undefined ? { reviewerId: input.reviewerId } : {},
50148
+ ...input.comments !== undefined ? { comments: input.comments } : {}
50149
+ };
50150
+ };
50151
+ var DEFAULT_VOICE_SALES_RUBRIC = {
50152
+ criteria: [
50153
+ {
50154
+ id: "greeting",
50155
+ label: "Professional greeting",
50156
+ section: "opening",
50157
+ weight: 1
50158
+ },
50159
+ {
50160
+ id: "needs-discovery",
50161
+ label: "Discovers customer needs",
50162
+ required: true,
50163
+ section: "discovery",
50164
+ weight: 2
50165
+ },
50166
+ {
50167
+ id: "objection-handling",
50168
+ label: "Handles objections clearly",
50169
+ section: "objections",
50170
+ weight: 2
50171
+ },
50172
+ {
50173
+ id: "compliance-disclosure",
50174
+ label: "Made required compliance disclosure",
50175
+ required: true,
50176
+ section: "compliance",
50177
+ weight: 3
50178
+ },
50179
+ {
50180
+ id: "close-or-next-step",
50181
+ label: "Closes or sets a next step",
50182
+ section: "close",
50183
+ weight: 2
50184
+ }
50185
+ ],
50186
+ id: "default-sales",
50187
+ label: "Default sales QA rubric",
50188
+ passingGrade: 0.75,
50189
+ scaleMax: 5
50190
+ };
50191
+ // src/aiScorecard.ts
50192
+ var DEFAULT_SYSTEM_PROMPT3 = "You are an impartial quality reviewer scoring a voice-agent call transcript. " + "For each criterion, return a numeric score between 0 and the rubric's scaleMax, with a one-sentence rationale grounded in the transcript. " + 'Respond with strict JSON: {"scores":[{"criterionId":"\u2026","score":4,"rationale":"\u2026"}],"comments":"\u2026"}. ' + "Do not return prose outside the JSON.";
50193
+ var buildPrompt2 = (input) => {
50194
+ const { rubric } = input;
50195
+ const scaleMax = rubric.scaleMax ?? 5;
50196
+ const criteriaBlock = rubric.criteria.map((criterion) => `- ${criterion.id}${criterion.required ? " (required)" : ""}: ${criterion.label} (weight=${criterion.weight}${criterion.section ? `, section=${criterion.section}` : ""})`).join(`
50197
+ `);
50198
+ const metadataBlock = input.metadata ? `
50199
+ Metadata:
50200
+ ${JSON.stringify(input.metadata, null, 2)}
50201
+ ` : "";
50202
+ return `Rubric: ${rubric.label} (scaleMax=${scaleMax})
50203
+ Criteria:
50204
+ ${criteriaBlock}
50205
+ ${metadataBlock}
50206
+ Transcript:
50207
+ ${input.transcript}
50208
+
50209
+ Return JSON only.`;
50210
+ };
50211
+ var extractJson3 = (raw) => {
50212
+ const trimmed = raw.trim();
50213
+ if (!trimmed)
50214
+ throw new Error("AI scorecard returned an empty response");
50215
+ const fenced = /```(?:json)?\s*([\s\S]*?)```/iu.exec(trimmed);
50216
+ const candidate = fenced ? fenced[1].trim() : trimmed;
50217
+ try {
50218
+ return JSON.parse(candidate);
50219
+ } catch {
50220
+ const start = candidate.indexOf("{");
50221
+ const end = candidate.lastIndexOf("}");
50222
+ if (start >= 0 && end > start) {
50223
+ return JSON.parse(candidate.slice(start, end + 1));
50224
+ }
50225
+ throw new Error(`AI scorecard response was not valid JSON: ${raw.slice(0, 200)}`);
50226
+ }
50227
+ };
50228
+ var parseVoiceAIScorecardResponse = (raw, rubric) => {
50229
+ const payload = extractJson3(raw);
50230
+ if (!payload || typeof payload !== "object") {
50231
+ throw new Error("AI scorecard response is not a JSON object");
50232
+ }
50233
+ const root = payload;
50234
+ const scoresRaw = root.scores;
50235
+ if (!Array.isArray(scoresRaw)) {
50236
+ throw new Error("AI scorecard response missing scores[] array");
50237
+ }
50238
+ const known = new Set(rubric.criteria.map((c) => c.id));
50239
+ const parsed = [];
50240
+ for (const entry of scoresRaw) {
50241
+ if (!entry || typeof entry !== "object")
50242
+ continue;
50243
+ const item = entry;
50244
+ const criterionId = String(item.criterionId ?? "").trim();
50245
+ if (!criterionId || !known.has(criterionId))
50246
+ continue;
50247
+ const scoreValue = Number(item.score);
50248
+ if (Number.isNaN(scoreValue))
50249
+ continue;
50250
+ parsed.push({
50251
+ criterionId,
50252
+ score: scoreValue,
50253
+ ...typeof item.rationale === "string" ? { rationale: item.rationale } : {}
50254
+ });
50255
+ }
50256
+ const comments = typeof root.comments === "string" ? root.comments : undefined;
50257
+ return {
50258
+ scores: parsed,
50259
+ ...comments !== undefined ? { comments } : {}
50260
+ };
50261
+ };
50262
+ var createVoiceAIScorecard = (options) => {
50263
+ const systemPrompt = options.systemPrompt ?? DEFAULT_SYSTEM_PROMPT3;
50264
+ return {
50265
+ async scoreCall(input) {
50266
+ const prompt = buildPrompt2(input);
50267
+ const raw = await options.completion({ prompt, systemPrompt });
50268
+ const parsed = parseVoiceAIScorecardResponse(raw, input.rubric);
50269
+ const scoreMap = {};
50270
+ for (const entry of parsed.scores) {
50271
+ scoreMap[entry.criterionId] = {
50272
+ score: entry.score,
50273
+ ...entry.rationale !== undefined ? { rationale: entry.rationale } : {}
50274
+ };
50275
+ }
50276
+ for (const criterion of input.rubric.criteria) {
50277
+ if (!scoreMap[criterion.id]) {
50278
+ scoreMap[criterion.id] = {
50279
+ rationale: "No rationale returned by AI scorer",
50280
+ score: 0
50281
+ };
50282
+ }
50283
+ }
50284
+ return buildVoiceCallScorecard({
50285
+ reviewer: "llm",
50286
+ rubric: input.rubric,
50287
+ scores: scoreMap,
50288
+ sessionId: input.sessionId,
50289
+ ...input.agentId !== undefined ? { agentId: input.agentId } : {},
50290
+ ...input.reviewerId !== undefined ? { reviewerId: input.reviewerId } : {},
50291
+ ...parsed.comments !== undefined ? { comments: parsed.comments } : {},
50292
+ ...input.now !== undefined ? { now: input.now } : {}
50293
+ });
50294
+ }
50295
+ };
50296
+ };
50297
+ // src/agentPerformanceReport.ts
50298
+ var bucketKeyFor = (ms, bucket) => {
50299
+ const date = new Date(ms);
50300
+ const year = date.getUTCFullYear();
50301
+ const month = String(date.getUTCMonth() + 1).padStart(2, "0");
50302
+ const day = String(date.getUTCDate()).padStart(2, "0");
50303
+ if (bucket === "day")
50304
+ return `${year}-${month}-${day}`;
50305
+ if (bucket === "month")
50306
+ return `${year}-${month}`;
50307
+ const firstJan = Date.UTC(year, 0, 1);
50308
+ const week = Math.floor((ms - firstJan) / (7 * 24 * 60 * 60 * 1000)) + 1;
50309
+ return `${year}-W${String(week).padStart(2, "0")}`;
50310
+ };
50311
+ var buildVoiceAgentPerformanceReport = (input) => {
50312
+ const bucket = input.bucket ?? "week";
50313
+ const scorecards = input.scorecards.filter((card) => card.agentId === input.agentId && card.rubricId === input.rubricId).filter((card) => (input.fromMs === undefined || card.createdAt >= input.fromMs) && (input.toMs === undefined || card.createdAt <= input.toMs)).sort((a, b) => a.createdAt - b.createdAt);
50314
+ const bucketMap = new Map;
50315
+ for (const card of scorecards) {
50316
+ const key = bucketKeyFor(card.createdAt, bucket);
50317
+ const entry = bucketMap.get(key) ?? {
50318
+ fail: 0,
50319
+ needsReview: 0,
50320
+ pass: 0,
50321
+ sum: 0,
50322
+ total: 0
50323
+ };
50324
+ entry.total += 1;
50325
+ entry.sum += card.weightedScore;
50326
+ if (card.grade === "pass")
50327
+ entry.pass += 1;
50328
+ else if (card.grade === "needs-review")
50329
+ entry.needsReview += 1;
50330
+ else
50331
+ entry.fail += 1;
50332
+ bucketMap.set(key, entry);
50333
+ }
50334
+ const buckets = Array.from(bucketMap.entries()).sort((a, b) => a[0] < b[0] ? -1 : 1).map(([bucketKey2, e]) => ({
50335
+ averageWeightedScore: e.total > 0 ? e.sum / e.total : 0,
50336
+ bucketKey: bucketKey2,
50337
+ callsScored: e.total,
50338
+ failRate: e.total > 0 ? e.fail / e.total : 0,
50339
+ needsReviewRate: e.total > 0 ? e.needsReview / e.total : 0,
50340
+ passRate: e.total > 0 ? e.pass / e.total : 0
50341
+ }));
50342
+ const criterionMap = new Map;
50343
+ for (const card of scorecards) {
50344
+ for (const result of card.results) {
50345
+ const entry = criterionMap.get(result.criterionId) ?? {
50346
+ firstAvg: null,
50347
+ passes: 0,
50348
+ scoreSum: 0,
50349
+ total: 0
50350
+ };
50351
+ entry.scoreSum += result.score / card.scaleMax;
50352
+ entry.total += 1;
50353
+ if (result.passed)
50354
+ entry.passes += 1;
50355
+ criterionMap.set(result.criterionId, entry);
50356
+ }
50357
+ }
50358
+ const firstHalf = scorecards.slice(0, Math.max(1, Math.floor(scorecards.length / 2)));
50359
+ const secondHalf = scorecards.slice(Math.floor(scorecards.length / 2));
50360
+ const halfAverage = (cards, criterionId) => {
50361
+ const matches = cards.flatMap((c) => c.results.filter((r) => r.criterionId === criterionId).map((r) => r.score / c.scaleMax));
50362
+ if (matches.length === 0)
50363
+ return null;
50364
+ return matches.reduce((a, b) => a + b, 0) / matches.length;
50365
+ };
50366
+ const criteria = [];
50367
+ for (const [criterionId, e] of criterionMap) {
50368
+ const earlier = halfAverage(firstHalf, criterionId);
50369
+ const later = halfAverage(secondHalf, criterionId);
50370
+ let trend = "flat";
50371
+ let delta = 0;
50372
+ if (earlier !== null && later !== null) {
50373
+ delta = later - earlier;
50374
+ if (delta > 0.05)
50375
+ trend = "up";
50376
+ else if (delta < -0.05)
50377
+ trend = "down";
50378
+ }
50379
+ criteria.push({
50380
+ averageScore: e.total > 0 ? e.scoreSum / e.total : 0,
50381
+ criterionId,
50382
+ delta,
50383
+ passRate: e.total > 0 ? e.passes / e.total : 0,
50384
+ trend
50385
+ });
50386
+ }
50387
+ criteria.sort((a, b) => a.criterionId.localeCompare(b.criterionId));
50388
+ const overallTotal = scorecards.length;
50389
+ const overallSum = scorecards.reduce((s, c) => s + c.weightedScore, 0);
50390
+ const overallPasses = scorecards.filter((c) => c.grade === "pass").length;
50391
+ const ranked = [...criteria].sort((a, b) => a.averageScore - b.averageScore);
50392
+ return {
50393
+ agentId: input.agentId,
50394
+ bestCriterion: ranked.at(-1)?.criterionId ?? null,
50395
+ bucket,
50396
+ buckets,
50397
+ criteria,
50398
+ fromMs: input.fromMs ?? scorecards[0]?.createdAt ?? 0,
50399
+ overallAverageScore: overallTotal > 0 ? overallSum / overallTotal : 0,
50400
+ overallPassRate: overallTotal > 0 ? overallPasses / overallTotal : 0,
50401
+ rubricId: input.rubricId,
50402
+ toMs: input.toMs ?? scorecards.at(-1)?.createdAt ?? 0,
50403
+ totalCalls: overallTotal,
50404
+ worstCriterion: ranked[0]?.criterionId ?? null
50405
+ };
50406
+ };
50407
+ // src/scorecardCalibration.ts
50408
+ var normalize = (raw, scaleMax) => scaleMax === 0 ? 0 : raw / scaleMax;
50409
+ var correlation = (xs, ys) => {
50410
+ if (xs.length === 0 || xs.length !== ys.length)
50411
+ return 0;
50412
+ const meanX = xs.reduce((a, b) => a + b, 0) / xs.length;
50413
+ const meanY = ys.reduce((a, b) => a + b, 0) / ys.length;
50414
+ let num = 0;
50415
+ let denomX = 0;
50416
+ let denomY = 0;
50417
+ for (let i = 0;i < xs.length; i++) {
50418
+ const dx = (xs[i] ?? 0) - meanX;
50419
+ const dy = (ys[i] ?? 0) - meanY;
50420
+ num += dx * dy;
50421
+ denomX += dx * dx;
50422
+ denomY += dy * dy;
50423
+ }
50424
+ if (denomX === 0 || denomY === 0)
50425
+ return 0;
50426
+ return num / Math.sqrt(denomX * denomY);
50427
+ };
50428
+ var computeVoiceScorecardCalibration = (pairs, options = {}) => {
50429
+ if (pairs.length === 0) {
50430
+ return {
50431
+ gradeAgreementRate: 0,
50432
+ meanAbsoluteError: 0,
50433
+ pairsCompared: 0,
50434
+ perCriterion: [],
50435
+ rootMeanSquareError: 0,
50436
+ weightedScoreCorrelation: 0,
50437
+ worstDivergences: []
50438
+ };
50439
+ }
50440
+ const topN = options.topDivergences ?? 10;
50441
+ const gapsByCriterion = new Map;
50442
+ const allGaps = [];
50443
+ const divergences = [];
50444
+ const humanWeighted = [];
50445
+ const llmWeighted = [];
50446
+ let gradeAgreed = 0;
50447
+ let comparedPairs = 0;
50448
+ for (const pair of pairs) {
50449
+ if (pair.human.rubricId !== pair.llm.rubricId)
50450
+ continue;
50451
+ comparedPairs += 1;
50452
+ if (pair.human.grade === pair.llm.grade)
50453
+ gradeAgreed += 1;
50454
+ humanWeighted.push(pair.human.weightedScore);
50455
+ llmWeighted.push(pair.llm.weightedScore);
50456
+ const humanByCriterion = new Map(pair.human.results.map((r) => [r.criterionId, r]));
50457
+ const llmByCriterion = new Map(pair.llm.results.map((r) => [r.criterionId, r]));
50458
+ const criteriaIds = new Set([
50459
+ ...humanByCriterion.keys(),
50460
+ ...llmByCriterion.keys()
50461
+ ]);
50462
+ for (const criterionId of criteriaIds) {
50463
+ const h = humanByCriterion.get(criterionId);
50464
+ const l = llmByCriterion.get(criterionId);
50465
+ if (!h || !l)
50466
+ continue;
50467
+ const hn = normalize(h.score, pair.human.scaleMax);
50468
+ const ln = normalize(l.score, pair.llm.scaleMax);
50469
+ const gap = Math.abs(hn - ln);
50470
+ allGaps.push(gap);
50471
+ divergences.push({
50472
+ criterionId,
50473
+ humanScore: hn,
50474
+ llmScore: ln,
50475
+ normalizedGap: hn - ln,
50476
+ sessionId: pair.sessionId
50477
+ });
50478
+ const entry = gapsByCriterion.get(criterionId) ?? {
50479
+ absSum: 0,
50480
+ biasSum: 0,
50481
+ count: 0,
50482
+ humanSum: 0,
50483
+ llmSum: 0
50484
+ };
50485
+ entry.absSum += gap;
50486
+ entry.biasSum += ln - hn;
50487
+ entry.humanSum += hn;
50488
+ entry.llmSum += ln;
50489
+ entry.count += 1;
50490
+ gapsByCriterion.set(criterionId, entry);
50491
+ }
50492
+ }
50493
+ const mae = allGaps.length === 0 ? 0 : allGaps.reduce((a, b) => a + b, 0) / allGaps.length;
50494
+ const rmse = allGaps.length === 0 ? 0 : Math.sqrt(allGaps.reduce((a, b) => a + b * b, 0) / allGaps.length);
50495
+ const perCriterion = Array.from(gapsByCriterion.entries()).map(([criterionId, e]) => ({
50496
+ averageHumanScore: e.count === 0 ? 0 : e.humanSum / e.count,
50497
+ averageLLMScore: e.count === 0 ? 0 : e.llmSum / e.count,
50498
+ bias: e.count === 0 ? 0 : e.biasSum / e.count,
50499
+ criterionId,
50500
+ meanAbsoluteError: e.count === 0 ? 0 : e.absSum / e.count
50501
+ }));
50502
+ return {
50503
+ gradeAgreementRate: comparedPairs === 0 ? 0 : gradeAgreed / comparedPairs,
50504
+ meanAbsoluteError: mae,
50505
+ pairsCompared: comparedPairs,
50506
+ perCriterion,
50507
+ rootMeanSquareError: rmse,
50508
+ weightedScoreCorrelation: correlation(humanWeighted, llmWeighted),
50509
+ worstDivergences: divergences.sort((a, b) => Math.abs(b.normalizedGap) - Math.abs(a.normalizedGap)).slice(0, topN)
50510
+ };
50511
+ };
50512
+ // src/qualityDriftDetector.ts
50513
+ var severityFor = (delta, watch, regression) => {
50514
+ if (delta <= -regression)
50515
+ return "regression";
50516
+ if (delta <= -watch)
50517
+ return "watch";
50518
+ return "ok";
50519
+ };
50520
+ var averageScore = (cards) => cards.length === 0 ? 0 : cards.reduce((sum, c) => sum + c.weightedScore, 0) / cards.length;
50521
+ var averageCriterion = (cards, criterionId) => {
50522
+ const matches = [];
50523
+ for (const card of cards) {
50524
+ for (const result of card.results) {
50525
+ if (result.criterionId === criterionId) {
50526
+ matches.push(result.score / card.scaleMax);
50527
+ }
50528
+ }
50529
+ }
50530
+ return matches.length === 0 ? 0 : matches.reduce((a, b) => a + b, 0) / matches.length;
50531
+ };
50532
+ var detectVoiceQualityDrift = (input) => {
50533
+ const now = input.now ?? (() => Date.now());
50534
+ const currentWindow = input.currentWindowMs ?? 7 * 24 * 60 * 60 * 1000;
50535
+ const baselineWindow = input.baselineWindowMs ?? 30 * 24 * 60 * 60 * 1000;
50536
+ const watch = input.watchThreshold ?? 0.05;
50537
+ const regression = input.regressionThreshold ?? 0.1;
50538
+ const cutoff = now();
50539
+ const currentFrom = cutoff - currentWindow;
50540
+ const baselineFrom = cutoff - currentWindow - baselineWindow;
50541
+ const baselineTo = currentFrom;
50542
+ const relevant = input.scorecards.filter((card) => card.rubricId === input.rubricId);
50543
+ const baselineCards = relevant.filter((c) => c.createdAt >= baselineFrom && c.createdAt < baselineTo);
50544
+ const currentCards = relevant.filter((c) => c.createdAt >= currentFrom && c.createdAt <= cutoff);
50545
+ const baselineAvg = averageScore(baselineCards);
50546
+ const currentAvg = averageScore(currentCards);
50547
+ const overallDelta = currentAvg - baselineAvg;
50548
+ const criterionIds = new Set;
50549
+ for (const card of relevant) {
50550
+ for (const result of card.results)
50551
+ criterionIds.add(result.criterionId);
50552
+ }
50553
+ const criteria = [];
50554
+ for (const criterionId of criterionIds) {
50555
+ const baseline = averageCriterion(baselineCards, criterionId);
50556
+ const current = averageCriterion(currentCards, criterionId);
50557
+ const delta = current - baseline;
50558
+ const severity = severityFor(delta, watch, regression);
50559
+ criteria.push({
50560
+ baselineAverage: baseline,
50561
+ criterionId,
50562
+ currentAverage: current,
50563
+ delta,
50564
+ severity
50565
+ });
50566
+ }
50567
+ criteria.sort((a, b) => a.delta - b.delta);
50568
+ const alertCount = criteria.filter((c) => c.severity !== "ok").length;
50569
+ return {
50570
+ alertCount,
50571
+ baselineWindow: {
50572
+ from: baselineFrom,
50573
+ sampleSize: baselineCards.length,
50574
+ to: baselineTo
50575
+ },
50576
+ criteria,
50577
+ currentWindow: {
50578
+ from: currentFrom,
50579
+ sampleSize: currentCards.length,
50580
+ to: cutoff
50581
+ },
50582
+ overall: {
50583
+ baselineAverage: baselineAvg,
50584
+ currentAverage: currentAvg,
50585
+ delta: overallDelta,
50586
+ severity: severityFor(overallDelta, watch, regression)
50587
+ },
50588
+ rubricId: input.rubricId,
50589
+ scope: { from: baselineFrom, to: cutoff }
50590
+ };
50591
+ };
49605
50592
  export {
49606
50593
  writeVoiceProofPack,
49607
50594
  writeVoiceMediaPipelineArtifacts,
@@ -49649,6 +50636,7 @@ export {
49649
50636
  summarizeVoiceOpsTaskQueue,
49650
50637
  summarizeVoiceOpsTaskAnalytics,
49651
50638
  summarizeVoiceOpsStatus,
50639
+ summarizeVoiceNoShowVerdict,
49652
50640
  summarizeVoiceMediaPipelineReport,
49653
50641
  summarizeVoiceLiveLatency,
49654
50642
  summarizeVoiceIntegrationEvents,
@@ -49657,6 +50645,7 @@ export {
49657
50645
  summarizeVoiceCampaigns,
49658
50646
  summarizeVoiceCampaignDispositions,
49659
50647
  summarizeVoiceCallerTranscript,
50648
+ summarizeVoiceCalendarSlot,
49660
50649
  summarizeVoiceBrowserMedia,
49661
50650
  summarizeVoiceBargeIn,
49662
50651
  summarizeVoiceAuditTrail,
@@ -49670,6 +50659,7 @@ export {
49670
50659
  shouldRetryCampaignAttempt,
49671
50660
  shapeTelephonyAssistantText,
49672
50661
  selectVoiceTraceEventsForPrune,
50662
+ scoreVoiceNoShowRisk,
49673
50663
  saveVoiceIncidentBundleArtifact,
49674
50664
  runVoiceToolContractSuite,
49675
50665
  runVoiceToolContract,
@@ -49851,6 +50841,7 @@ export {
49851
50841
  predictVoiceCallCost,
49852
50842
  parseVoiceTelephonyWebhookEvent,
49853
50843
  parseVoiceSessionSnapshot,
50844
+ parseVoiceAIScorecardResponse,
49854
50845
  normalizeVoiceProofTrendReport,
49855
50846
  normalizePhoneNumber,
49856
50847
  muteVoiceMonitorIssue,
@@ -49877,6 +50868,7 @@ export {
49877
50868
  getLatestVoiceTelephonyMediaReport,
49878
50869
  getLatestVoiceBrowserMediaReport,
49879
50870
  getDefaultVoiceTelephonyBenchmarkScenarios,
50871
+ generateVoiceCalendarSlots,
49880
50872
  fromVapiAssistantConfig,
49881
50873
  formatVoiceProofTrendAge,
49882
50874
  formatVoiceCallPlayerTimestamp,
@@ -49928,6 +50920,7 @@ export {
49928
50920
  encodeTwilioMulawBase64,
49929
50921
  encodeStereoWav,
49930
50922
  encodePcmAsWav,
50923
+ detectVoiceQualityDrift,
49931
50924
  describeVoiceIVRPlan,
49932
50925
  describeVoiceAssistantMode,
49933
50926
  describeVoiceAgentUIState,
@@ -50053,6 +51046,7 @@ export {
50053
51046
  createVoiceRetentionScheduler,
50054
51047
  createVoiceResilienceRoutes,
50055
51048
  createVoiceReplayTimelineHTMXRoute,
51049
+ createVoiceReminderScheduler,
50056
51050
  createVoiceRedisTelnyxWebhookEventStore,
50057
51051
  createVoiceRedisTelephonyWebhookIdempotencyStore,
50058
51052
  createVoiceRedisTaskLeaseCoordinator,
@@ -50193,6 +51187,7 @@ export {
50193
51187
  createVoiceIncidentBundleRoutes,
50194
51188
  createVoiceInMemoryRealCallProfileRecoveryJobStore,
50195
51189
  createVoiceInMemoryMonitorRegistry,
51190
+ createVoiceInMemoryCalendarAdapter,
50196
51191
  createVoiceIVRSession,
50197
51192
  createVoiceHubSpotTaskUpdateSink,
50198
51193
  createVoiceHubSpotTaskSyncSinks,
@@ -50267,6 +51262,7 @@ export {
50267
51262
  createVoiceCRMActivitySink,
50268
51263
  createVoiceBrowserMediaRoutes,
50269
51264
  createVoiceBrowserCallProfileRoutes,
51265
+ createVoiceBookingFlow,
50270
51266
  createVoiceBearerAuthVerifier,
50271
51267
  createVoiceBargeInRoutes,
50272
51268
  createVoiceBackchannelDriver,
@@ -50293,6 +51289,7 @@ export {
50293
51289
  createVoiceAgentTool,
50294
51290
  createVoiceAgentSquad,
50295
51291
  createVoiceAgent,
51292
+ createVoiceAIScorecard,
50296
51293
  createVoiceAIJudgeCompletion,
50297
51294
  createTwilioVoiceRoutes,
50298
51295
  createTwilioVoiceResponse,
@@ -50332,6 +51329,7 @@ export {
50332
51329
  createAnthropicVoiceAssistantModel,
50333
51330
  createAIVoiceModel,
50334
51331
  conditionAudioChunk,
51332
+ computeVoiceScorecardCalibration,
50335
51333
  computePcmDurationMs,
50336
51334
  completeVoiceOpsTask,
50337
51335
  compareVoiceEvalBaseline,
@@ -50425,11 +51423,13 @@ export {
50425
51423
  buildVoiceCompetitiveCoverageReport,
50426
51424
  buildVoiceCampaignObservabilityReport,
50427
51425
  buildVoiceCallerMemoryNamespace,
51426
+ buildVoiceCallScorecard,
50428
51427
  buildVoiceCallDebuggerReport,
50429
51428
  buildVoiceBrowserCallProfileReport,
50430
51429
  buildVoiceAuditTrailReport,
50431
51430
  buildVoiceAuditExport,
50432
51431
  buildVoiceAuditDeliveryReport,
51432
+ buildVoiceAgentPerformanceReport,
50433
51433
  buildReplayTimelineReport,
50434
51434
  buildOTELTraceId,
50435
51435
  buildOTELSpanId,
@@ -50494,6 +51494,8 @@ export {
50494
51494
  VOICE_DTMF_DIGITS,
50495
51495
  VOICE_CALLER_MEMORY_KEY,
50496
51496
  TURN_PROFILE_DEFAULTS,
51497
+ DEFAULT_VOICE_SALES_RUBRIC,
51498
+ DEFAULT_VOICE_REMINDER_TRIGGERS,
50497
51499
  DEFAULT_VOICE_REDACTION_PATTERNS,
50498
51500
  DEFAULT_VOICE_PROOF_TREND_PROFILE_DEFINITIONS,
50499
51501
  DEFAULT_VOICE_PROOF_TRENDS_MAX_AGE_MS,