@absolutejs/voice 0.0.22-beta.534 → 0.0.22-beta.535
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/angular/index.js +347 -347
- package/dist/angular/voice-cost-dashboard.service.d.ts +1 -1
- package/dist/angular/voice-replay-timeline.service.d.ts +1 -1
- package/dist/client/agentSquadStatusWidget.d.ts +3 -3
- package/dist/client/browserVoiceSupport.d.ts +1 -1
- package/dist/client/callDebugger.d.ts +2 -2
- package/dist/client/callDebuggerWidget.d.ts +3 -3
- package/dist/client/campaignDialerProof.d.ts +4 -4
- package/dist/client/deliveryRuntime.d.ts +4 -4
- package/dist/client/deliveryRuntimeWidget.d.ts +3 -3
- package/dist/client/htmxAttributes.d.ts +1 -5
- package/dist/client/htmxBootstrap.js +82 -82
- package/dist/client/htmxDashboardRenderers.d.ts +17 -17
- package/dist/client/index.js +2478 -2484
- package/dist/client/liveOps.d.ts +2 -2
- package/dist/client/liveOpsWidget.d.ts +3 -3
- package/dist/client/opsActionCenter.d.ts +3 -3
- package/dist/client/opsActionCenterWidget.d.ts +3 -3
- package/dist/client/opsActionHistory.d.ts +2 -2
- package/dist/client/opsActionHistoryWidget.d.ts +2 -2
- package/dist/client/opsStatus.d.ts +2 -2
- package/dist/client/opsStatusWidget.d.ts +4 -4
- package/dist/client/platformCoverage.d.ts +2 -2
- package/dist/client/platformCoverageWidget.d.ts +3 -3
- package/dist/client/profileComparison.d.ts +2 -2
- package/dist/client/profileComparisonWidget.d.ts +3 -3
- package/dist/client/profileSwitchRecommendation.d.ts +2 -2
- package/dist/client/profileSwitchRecommendationWidget.d.ts +3 -3
- package/dist/client/proofTrends.d.ts +2 -2
- package/dist/client/proofTrendsWidget.d.ts +3 -3
- package/dist/client/providerCapabilities.d.ts +2 -2
- package/dist/client/providerCapabilitiesWidget.d.ts +3 -3
- package/dist/client/providerContracts.d.ts +2 -2
- package/dist/client/providerContractsWidget.d.ts +3 -3
- package/dist/client/providerSimulationControls.d.ts +1 -1
- package/dist/client/providerSimulationControlsWidget.d.ts +4 -4
- package/dist/client/providerStatus.d.ts +2 -2
- package/dist/client/providerStatusWidget.d.ts +3 -3
- package/dist/client/readinessFailures.d.ts +2 -2
- package/dist/client/readinessFailuresWidget.d.ts +3 -3
- package/dist/client/reconnectProfileEvidence.d.ts +2 -2
- package/dist/client/reconnectProfileEvidenceWidget.d.ts +3 -3
- package/dist/client/routingStatus.d.ts +2 -2
- package/dist/client/routingStatusWidget.d.ts +3 -3
- package/dist/client/sessionObservability.d.ts +2 -2
- package/dist/client/sessionObservabilityWidget.d.ts +3 -3
- package/dist/client/sessionSnapshot.d.ts +2 -2
- package/dist/client/sessionSnapshotWidget.d.ts +2 -2
- package/dist/client/traceTimeline.d.ts +2 -2
- package/dist/client/traceTimelineWidget.d.ts +3 -3
- package/dist/client/turnLatency.d.ts +4 -4
- package/dist/client/turnLatencyWidget.d.ts +3 -3
- package/dist/client/turnQuality.d.ts +2 -2
- package/dist/client/turnQualityWidget.d.ts +3 -3
- package/dist/client/workflowStatus.d.ts +2 -2
- package/dist/core/agent.d.ts +1 -1
- package/dist/core/agentSquadContract.d.ts +2 -2
- package/dist/core/agentState.d.ts +1 -1
- package/dist/core/aiScorecard.d.ts +1 -1
- package/dist/core/assistant.d.ts +1 -1
- package/dist/core/assistantHealth.d.ts +3 -3
- package/dist/core/assistantMemory.d.ts +7 -7
- package/dist/core/audioConditioning.d.ts +1 -1
- package/dist/core/audit.d.ts +6 -6
- package/dist/core/auditDeliveryRoutes.d.ts +7 -7
- package/dist/core/auditExport.d.ts +10 -10
- package/dist/core/auditRoutes.d.ts +5 -5
- package/dist/core/auditSinks.d.ts +7 -7
- package/dist/core/bargeInRoutes.d.ts +6 -6
- package/dist/core/bookingFlow.d.ts +1 -1
- package/dist/core/browserCallProfiles.d.ts +3 -3
- package/dist/core/browserMediaRoutes.d.ts +5 -5
- package/dist/core/callDebugger.d.ts +1 -1
- package/dist/core/callDisposition.d.ts +1 -1
- package/dist/core/callScorecard.d.ts +1 -1
- package/dist/core/campaign.d.ts +5 -5
- package/dist/core/campaignControls.d.ts +1 -1
- package/dist/core/campaignDialers.d.ts +4 -4
- package/dist/core/campaignTemplate.d.ts +1 -1
- package/dist/core/competitiveCoverage.d.ts +2 -2
- package/dist/core/conversationSimulator.d.ts +1 -1
- package/dist/core/correction.d.ts +1 -1
- package/dist/core/dataControl.d.ts +5 -5
- package/dist/core/deliveryRuntime.d.ts +7 -7
- package/dist/core/deliverySinkRoutes.d.ts +7 -7
- package/dist/core/demoReadyRoutes.d.ts +1 -1
- package/dist/core/dncRegistry.d.ts +1 -1
- package/dist/core/dtmfCollector.d.ts +1 -1
- package/dist/core/evalRoutes.d.ts +16 -16
- package/dist/core/fileStore.d.ts +18 -18
- package/dist/core/guardrails.d.ts +2 -2
- package/dist/core/handoff.d.ts +4 -4
- package/dist/core/handoffHealth.d.ts +4 -4
- package/dist/core/htmx.d.ts +1 -1
- package/dist/core/incidentBundle.d.ts +1 -1
- package/dist/core/incidentTimeline.d.ts +11 -11
- package/dist/core/latencySlo.d.ts +1 -1
- package/dist/core/liveCoach.d.ts +1 -1
- package/dist/core/liveLatency.d.ts +3 -3
- package/dist/core/liveOps.d.ts +6 -6
- package/dist/core/mediaPipelineRoutes.d.ts +4 -4
- package/dist/core/monitor.d.ts +1 -1
- package/dist/core/multilingualProof.d.ts +1 -1
- package/dist/core/observabilityExport.d.ts +15 -15
- package/dist/core/operationalStatus.d.ts +3 -3
- package/dist/core/operationsRecord.d.ts +8 -8
- package/dist/core/ops.d.ts +58 -58
- package/dist/core/opsActionAuditRoutes.d.ts +10 -10
- package/dist/core/opsConsoleRoutes.d.ts +3 -3
- package/dist/core/opsRecovery.d.ts +4 -4
- package/dist/core/opsSinks.d.ts +6 -6
- package/dist/core/opsStatusRoutes.d.ts +3 -3
- package/dist/core/opsWebhook.d.ts +9 -9
- package/dist/core/otelExporter.d.ts +1 -1
- package/dist/core/outcomeContract.d.ts +6 -6
- package/dist/core/pathway.d.ts +2 -2
- package/dist/core/pathwayRuntime.d.ts +2 -2
- package/dist/core/phoneAgent.d.ts +2 -2
- package/dist/core/phoneAgentProductionSmoke.d.ts +7 -7
- package/dist/core/platformCoverage.d.ts +1 -1
- package/dist/core/postCallSurvey.d.ts +1 -1
- package/dist/core/postgresStore.d.ts +8 -8
- package/dist/core/productionReadiness.d.ts +9 -9
- package/dist/core/profileSwitchRecommendation.d.ts +9 -9
- package/dist/core/proofAssertions.d.ts +1 -1
- package/dist/core/proofPack.d.ts +12 -12
- package/dist/core/proofRunner.d.ts +2 -2
- package/dist/core/proofTrends.d.ts +26 -26
- package/dist/core/providerCapabilities.d.ts +5 -5
- package/dist/core/providerDecisionTraces.d.ts +4 -4
- package/dist/core/providerHealth.d.ts +3 -3
- package/dist/core/providerOrchestration.d.ts +4 -4
- package/dist/core/providerRouterTraces.d.ts +2 -2
- package/dist/core/providerRoutingContract.d.ts +2 -2
- package/dist/core/providerSlo.d.ts +5 -5
- package/dist/core/providerStackRecommendations.d.ts +8 -8
- package/dist/core/qualityRoutes.d.ts +3 -3
- package/dist/core/queue.d.ts +26 -26
- package/dist/core/realtimeChannel.d.ts +5 -5
- package/dist/core/realtimeProviderContracts.d.ts +3 -3
- package/dist/core/reconnectContract.d.ts +16 -16
- package/dist/core/recordingStore.d.ts +2 -2
- package/dist/core/reminderScheduler.d.ts +1 -1
- package/dist/core/resilienceRoutes.d.ts +1 -1
- package/dist/core/routing.d.ts +1 -1
- package/dist/core/sessionObservability.d.ts +2 -2
- package/dist/core/sessionReplay.d.ts +12 -12
- package/dist/core/sessionSnapshot.d.ts +1 -1
- package/dist/core/simulationSuite.d.ts +4 -4
- package/dist/core/sloCalibration.d.ts +12 -12
- package/dist/core/sqliteStore.d.ts +8 -8
- package/dist/core/telephonyMediaRoutes.d.ts +4 -4
- package/dist/core/telephonyOutcome.d.ts +2 -2
- package/dist/core/toolContract.d.ts +10 -10
- package/dist/core/toolRuntime.d.ts +1 -1
- package/dist/core/trace.d.ts +18 -18
- package/dist/core/traceDeliveryRoutes.d.ts +7 -7
- package/dist/core/traceTimeline.d.ts +3 -3
- package/dist/core/turnLatency.d.ts +4 -4
- package/dist/core/turnQuality.d.ts +5 -5
- package/dist/core/types.d.ts +7 -2
- package/dist/core/voiceMonitoring.d.ts +11 -11
- package/dist/core/webhookVerification.d.ts +4 -4
- package/dist/core/whisperChannel.d.ts +4 -4
- package/dist/core/workflowContract.d.ts +13 -13
- package/dist/core/zeroDataRetention.d.ts +3 -13
- package/dist/drizzle/assistantMemory.d.ts +95 -0
- package/dist/drizzle/eval.d.ts +61 -0
- package/dist/drizzle/handoff.d.ts +62 -0
- package/dist/drizzle/index.d.ts +1029 -0
- package/dist/drizzle/index.js +3028 -0
- package/dist/drizzle/observabilityExport.d.ts +61 -0
- package/dist/drizzle/proofTrends.d.ts +126 -0
- package/dist/drizzle/runtimeStorage.d.ts +1311 -0
- package/dist/drizzle/shared.d.ts +75 -0
- package/dist/embed/index.js +72 -72
- package/dist/embed/voice-widget.js +2 -2
- package/dist/index.js +7034 -7101
- package/dist/react/index.js +2148 -2150
- package/dist/svelte/createVoiceAgentSquadStatus.d.ts +2 -2
- package/dist/svelte/createVoiceCallDebugger.d.ts +1 -1
- package/dist/svelte/createVoiceCallPlayer.d.ts +8 -8
- package/dist/svelte/createVoiceCampaignDialerProof.d.ts +2 -2
- package/dist/svelte/createVoiceCostDashboard.d.ts +4 -4
- package/dist/svelte/createVoiceDeliveryRuntime.d.ts +3 -3
- package/dist/svelte/createVoiceLiveAgentConsole.d.ts +4 -4
- package/dist/svelte/createVoiceLiveOps.d.ts +4 -4
- package/dist/svelte/createVoiceOpsActionCenter.d.ts +2 -2
- package/dist/svelte/createVoiceOpsStatus.d.ts +2 -2
- package/dist/svelte/createVoiceProviderCapabilities.d.ts +1 -1
- package/dist/svelte/createVoiceProviderContracts.d.ts +1 -1
- package/dist/svelte/createVoiceProviderSimulationControls.d.ts +1 -1
- package/dist/svelte/createVoiceProviderStatus.d.ts +1 -1
- package/dist/svelte/createVoiceReplayTimeline.d.ts +1 -1
- package/dist/svelte/createVoiceRoutingStatus.d.ts +1 -1
- package/dist/svelte/createVoiceSessionObservability.d.ts +1 -1
- package/dist/svelte/createVoiceSessionSnapshot.d.ts +1 -1
- package/dist/svelte/createVoiceTraceTimeline.d.ts +1 -1
- package/dist/svelte/createVoiceTurnLatency.d.ts +2 -2
- package/dist/svelte/createVoiceTurnQuality.d.ts +1 -1
- package/dist/svelte/createVoiceWidget.d.ts +2 -2
- package/dist/svelte/createVoiceWorkflowStatus.d.ts +1 -1
- package/dist/svelte/index.js +1518 -1522
- package/dist/telephony/matrix.d.ts +3 -3
- package/dist/telephony/plivo.d.ts +2 -2
- package/dist/telephony/security.d.ts +3 -3
- package/dist/telephony/telnyx.d.ts +1 -1
- package/dist/telephony/twilio.d.ts +2 -2
- package/dist/testing/benchmark.d.ts +6 -6
- package/dist/testing/corrected.d.ts +4 -4
- package/dist/testing/duplex.d.ts +2 -2
- package/dist/testing/fixtures.d.ts +1 -1
- package/dist/testing/index.js +1382 -1388
- package/dist/testing/review.d.ts +4 -4
- package/dist/testing/sessionBenchmark.d.ts +10 -10
- package/dist/testing/telephony.d.ts +3 -3
- package/dist/testing/tts.d.ts +1 -1
- package/dist/vue/VoiceCostDashboard.d.ts +2 -2
- package/dist/vue/index.js +2110 -2112
- package/dist/vue/useVoiceController.d.ts +5 -5
- package/dist/vue/useVoiceDeliveryRuntime.d.ts +1 -1
- package/dist/vue/useVoiceStream.d.ts +5 -5
- package/package.json +28 -6
|
@@ -0,0 +1,3028 @@
|
|
|
1
|
+
// @bun
|
|
2
|
+
var __create = Object.create;
|
|
3
|
+
var __defProp = Object.defineProperty;
|
|
4
|
+
var __getOwnPropDesc = Object.getOwnPropertyDescriptor;
|
|
5
|
+
var __name = (target, name) => {
|
|
6
|
+
Object.defineProperty(target, "name", {
|
|
7
|
+
value: name,
|
|
8
|
+
enumerable: false,
|
|
9
|
+
configurable: true
|
|
10
|
+
});
|
|
11
|
+
return target;
|
|
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
|
+
};
|
|
26
|
+
var __knownSymbol = (name, symbol) => (symbol = Symbol[name]) ? symbol : Symbol.for("Symbol." + name);
|
|
27
|
+
var __typeError = (msg) => {
|
|
28
|
+
throw TypeError(msg);
|
|
29
|
+
};
|
|
30
|
+
var __defNormalProp = (obj, key, value) => (key in obj) ? __defProp(obj, key, { enumerable: true, configurable: true, writable: true, value }) : obj[key] = value;
|
|
31
|
+
var __accessCheck = (obj, member, msg) => member.has(obj) || __typeError("Cannot " + msg);
|
|
32
|
+
var __privateIn = (member, obj) => Object(obj) !== obj ? __typeError('Cannot use the "in" operator on this value') : member.has(obj);
|
|
33
|
+
var __privateGet = (obj, member, getter) => (__accessCheck(obj, member, "read from private field"), getter ? getter.call(obj) : member.get(obj));
|
|
34
|
+
var __privateSet = (obj, member, value, setter) => (__accessCheck(obj, member, "write to private field"), setter ? setter.call(obj, value) : member.set(obj, value), value);
|
|
35
|
+
var __privateMethod = (obj, member, method) => (__accessCheck(obj, member, "access private method"), method);
|
|
36
|
+
var __decoratorStart = (base) => [, , , __create(base?.[__knownSymbol("metadata")] ?? null)];
|
|
37
|
+
var __decoratorStrings = ["class", "method", "getter", "setter", "accessor", "field", "value", "get", "set"];
|
|
38
|
+
var __expectFn = (fn) => fn !== undefined && typeof fn !== "function" ? __typeError("Function expected") : fn;
|
|
39
|
+
var __decoratorContext = (kind, name, done, metadata, fns) => ({
|
|
40
|
+
kind: __decoratorStrings[kind],
|
|
41
|
+
name,
|
|
42
|
+
metadata,
|
|
43
|
+
addInitializer: (fn) => done._ ? __typeError("Already initialized") : fns.push(__expectFn(fn || null))
|
|
44
|
+
});
|
|
45
|
+
var __decoratorMetadata = (array, target) => __defNormalProp(target, __knownSymbol("metadata"), array[3]);
|
|
46
|
+
var __runInitializers = (array, flags, self, value) => {
|
|
47
|
+
for (var i = 0, fns = array[flags >> 1], n = fns && fns.length;i < n; i++)
|
|
48
|
+
flags & 1 ? fns[i].call(self) : value = fns[i].call(self, value);
|
|
49
|
+
return value;
|
|
50
|
+
};
|
|
51
|
+
var __decorateElement = (array, flags, name, decorators, target, extra) => {
|
|
52
|
+
var fn, it, done, ctx, access, k = flags & 7, s = !!(flags & 8), p = !!(flags & 16);
|
|
53
|
+
var j = k > 3 ? array.length + 1 : k ? s ? 1 : 2 : 0, key = __decoratorStrings[k + 5];
|
|
54
|
+
var initializers = k > 3 && (array[j - 1] = []), extraInitializers = array[j] || (array[j] = []);
|
|
55
|
+
var desc = k && (!p && !s && (target = target.prototype), k < 5 && (k > 3 || !p) && __getOwnPropDesc(k < 4 ? target : {
|
|
56
|
+
get [name]() {
|
|
57
|
+
return __privateGet(this, extra);
|
|
58
|
+
},
|
|
59
|
+
set [name](x) {
|
|
60
|
+
__privateSet(this, extra, x);
|
|
61
|
+
}
|
|
62
|
+
}, name));
|
|
63
|
+
k ? p && k < 4 && __name(extra, (k > 2 ? "set " : k > 1 ? "get " : "") + name) : __name(target, name);
|
|
64
|
+
for (var i = decorators.length - 1;i >= 0; i--) {
|
|
65
|
+
ctx = __decoratorContext(k, name, done = {}, array[3], extraInitializers);
|
|
66
|
+
if (k) {
|
|
67
|
+
ctx.static = s, ctx.private = p, access = ctx.access = { has: p ? (x) => __privateIn(target, x) : (x) => (name in x) };
|
|
68
|
+
if (k ^ 3)
|
|
69
|
+
access.get = p ? (x) => (k ^ 1 ? __privateGet : __privateMethod)(x, target, k ^ 4 ? extra : desc.get) : (x) => x[name];
|
|
70
|
+
if (k > 2)
|
|
71
|
+
access.set = p ? (x, y) => __privateSet(x, target, y, k ^ 4 ? extra : desc.set) : (x, y) => x[name] = y;
|
|
72
|
+
}
|
|
73
|
+
it = (0, decorators[i])(k ? k < 4 ? p ? extra : desc[key] : k > 4 ? undefined : { get: desc.get, set: desc.set } : target, ctx);
|
|
74
|
+
done._ = 1;
|
|
75
|
+
if (k ^ 4 || it === undefined)
|
|
76
|
+
__expectFn(it) && (k > 4 ? initializers.unshift(it) : k ? p ? extra = it : desc[key] = it : target = it);
|
|
77
|
+
else if (typeof it !== "object" || it === null)
|
|
78
|
+
__typeError("Object expected");
|
|
79
|
+
else
|
|
80
|
+
__expectFn(fn = it.get) && (desc.get = fn), __expectFn(fn = it.set) && (desc.set = fn), __expectFn(fn = it.init) && initializers.unshift(fn);
|
|
81
|
+
}
|
|
82
|
+
return k || __decoratorMetadata(array, target), desc && __defProp(target, name, desc), p ? k ^ 4 ? extra : desc : target;
|
|
83
|
+
};
|
|
84
|
+
var __require = import.meta.require;
|
|
85
|
+
|
|
86
|
+
// src/drizzle/assistantMemory.ts
|
|
87
|
+
import { and, desc, eq } from "drizzle-orm";
|
|
88
|
+
import { bigint, jsonb, pgTable, primaryKey, text } from "drizzle-orm/pg-core";
|
|
89
|
+
|
|
90
|
+
// src/core/assistantMemory.ts
|
|
91
|
+
var createMemoryId = (input) => `${input.assistantId}:${input.namespace}:${input.key}`;
|
|
92
|
+
var createVoiceAssistantMemoryHandle = async (input) => {
|
|
93
|
+
const namespace = await resolveVoiceAssistantMemoryNamespace({
|
|
94
|
+
assistantId: input.assistantId,
|
|
95
|
+
context: input.context,
|
|
96
|
+
memory: input.memory,
|
|
97
|
+
session: input.session
|
|
98
|
+
});
|
|
99
|
+
const trace = async (event) => {
|
|
100
|
+
await input.trace?.append({
|
|
101
|
+
at: Date.now(),
|
|
102
|
+
payload: {
|
|
103
|
+
assistantId: input.assistantId,
|
|
104
|
+
namespace,
|
|
105
|
+
...event
|
|
106
|
+
},
|
|
107
|
+
scenarioId: input.session.scenarioId,
|
|
108
|
+
sessionId: input.session.id,
|
|
109
|
+
type: "assistant.memory"
|
|
110
|
+
});
|
|
111
|
+
};
|
|
112
|
+
return {
|
|
113
|
+
namespace,
|
|
114
|
+
delete: async (key) => {
|
|
115
|
+
await input.memory.store.delete({
|
|
116
|
+
assistantId: input.assistantId,
|
|
117
|
+
key,
|
|
118
|
+
namespace
|
|
119
|
+
});
|
|
120
|
+
await trace({
|
|
121
|
+
action: "delete",
|
|
122
|
+
key
|
|
123
|
+
});
|
|
124
|
+
},
|
|
125
|
+
get: async (key) => {
|
|
126
|
+
const record = await input.memory.store.get({
|
|
127
|
+
assistantId: input.assistantId,
|
|
128
|
+
key,
|
|
129
|
+
namespace
|
|
130
|
+
});
|
|
131
|
+
await trace({
|
|
132
|
+
action: "get",
|
|
133
|
+
found: Boolean(record),
|
|
134
|
+
key
|
|
135
|
+
});
|
|
136
|
+
return record?.value;
|
|
137
|
+
},
|
|
138
|
+
list: async () => {
|
|
139
|
+
const records = await input.memory.store.list({
|
|
140
|
+
assistantId: input.assistantId,
|
|
141
|
+
namespace
|
|
142
|
+
});
|
|
143
|
+
await trace({
|
|
144
|
+
action: "list",
|
|
145
|
+
count: records.length
|
|
146
|
+
});
|
|
147
|
+
return records;
|
|
148
|
+
},
|
|
149
|
+
set: async (key, value, metadata) => {
|
|
150
|
+
const record = await input.memory.store.set({
|
|
151
|
+
assistantId: input.assistantId,
|
|
152
|
+
key,
|
|
153
|
+
metadata,
|
|
154
|
+
namespace,
|
|
155
|
+
value
|
|
156
|
+
});
|
|
157
|
+
await trace({
|
|
158
|
+
action: "set",
|
|
159
|
+
key
|
|
160
|
+
});
|
|
161
|
+
return record;
|
|
162
|
+
}
|
|
163
|
+
};
|
|
164
|
+
};
|
|
165
|
+
var createVoiceAssistantMemoryRecord = (input) => {
|
|
166
|
+
const now = Date.now();
|
|
167
|
+
return {
|
|
168
|
+
...input,
|
|
169
|
+
createdAt: input.createdAt ?? input.updatedAt ?? now,
|
|
170
|
+
updatedAt: input.updatedAt ?? now
|
|
171
|
+
};
|
|
172
|
+
};
|
|
173
|
+
var createVoiceMemoryAssistantMemoryStore = () => {
|
|
174
|
+
const records = new Map;
|
|
175
|
+
return {
|
|
176
|
+
delete: async (input) => {
|
|
177
|
+
records.delete(createMemoryId(input));
|
|
178
|
+
},
|
|
179
|
+
get: async (input) => records.get(createMemoryId(input)),
|
|
180
|
+
list: async (input) => [...records.values()].filter((record) => record.assistantId === input.assistantId && (input.namespace === undefined || record.namespace === input.namespace)).sort((left, right) => right.updatedAt - left.updatedAt),
|
|
181
|
+
set: async (input) => {
|
|
182
|
+
const id = createMemoryId(input);
|
|
183
|
+
const existing = records.get(id);
|
|
184
|
+
const record = createVoiceAssistantMemoryRecord({
|
|
185
|
+
...input,
|
|
186
|
+
createdAt: input.createdAt ?? existing?.createdAt,
|
|
187
|
+
updatedAt: input.updatedAt
|
|
188
|
+
});
|
|
189
|
+
records.set(id, record);
|
|
190
|
+
return record;
|
|
191
|
+
}
|
|
192
|
+
};
|
|
193
|
+
};
|
|
194
|
+
var resolveVoiceAssistantMemoryNamespace = async (input) => typeof input.memory.namespace === "function" ? await input.memory.namespace(input) : input.memory.namespace;
|
|
195
|
+
|
|
196
|
+
// src/drizzle/assistantMemory.ts
|
|
197
|
+
var voiceAssistantMemoryTable = pgTable("voice_assistant_memory", {
|
|
198
|
+
assistantId: text("assistant_id").notNull(),
|
|
199
|
+
key: text("key").notNull(),
|
|
200
|
+
namespace: text("namespace").notNull(),
|
|
201
|
+
payload: jsonb("payload").notNull(),
|
|
202
|
+
sortAt: bigint("sort_at", { mode: "number" }).notNull()
|
|
203
|
+
}, (table) => [
|
|
204
|
+
primaryKey({
|
|
205
|
+
columns: [table.assistantId, table.namespace, table.key]
|
|
206
|
+
})
|
|
207
|
+
]);
|
|
208
|
+
var createDrizzleAssistantMemoryStore = (db) => {
|
|
209
|
+
const get = async (input) => {
|
|
210
|
+
const rows = await db.select({ payload: voiceAssistantMemoryTable.payload }).from(voiceAssistantMemoryTable).where(and(eq(voiceAssistantMemoryTable.assistantId, input.assistantId), eq(voiceAssistantMemoryTable.namespace, input.namespace), eq(voiceAssistantMemoryTable.key, input.key))).limit(1);
|
|
211
|
+
return rows[0]?.payload;
|
|
212
|
+
};
|
|
213
|
+
return {
|
|
214
|
+
get,
|
|
215
|
+
delete: async (input) => {
|
|
216
|
+
await db.delete(voiceAssistantMemoryTable).where(and(eq(voiceAssistantMemoryTable.assistantId, input.assistantId), eq(voiceAssistantMemoryTable.namespace, input.namespace), eq(voiceAssistantMemoryTable.key, input.key)));
|
|
217
|
+
},
|
|
218
|
+
list: async (input) => {
|
|
219
|
+
const rows = await db.select({ payload: voiceAssistantMemoryTable.payload }).from(voiceAssistantMemoryTable).where(input.namespace === undefined ? eq(voiceAssistantMemoryTable.assistantId, input.assistantId) : and(eq(voiceAssistantMemoryTable.assistantId, input.assistantId), eq(voiceAssistantMemoryTable.namespace, input.namespace))).orderBy(desc(voiceAssistantMemoryTable.sortAt));
|
|
220
|
+
return rows.map((row) => row.payload);
|
|
221
|
+
},
|
|
222
|
+
set: async (input) => {
|
|
223
|
+
const existing = await get(input);
|
|
224
|
+
const record = createVoiceAssistantMemoryRecord({
|
|
225
|
+
...input,
|
|
226
|
+
createdAt: input.createdAt ?? existing?.createdAt,
|
|
227
|
+
updatedAt: input.updatedAt
|
|
228
|
+
});
|
|
229
|
+
await db.insert(voiceAssistantMemoryTable).values({
|
|
230
|
+
assistantId: record.assistantId,
|
|
231
|
+
key: record.key,
|
|
232
|
+
namespace: record.namespace,
|
|
233
|
+
payload: record,
|
|
234
|
+
sortAt: record.updatedAt
|
|
235
|
+
}).onConflictDoUpdate({
|
|
236
|
+
set: {
|
|
237
|
+
payload: record,
|
|
238
|
+
sortAt: record.updatedAt
|
|
239
|
+
},
|
|
240
|
+
target: [
|
|
241
|
+
voiceAssistantMemoryTable.assistantId,
|
|
242
|
+
voiceAssistantMemoryTable.namespace,
|
|
243
|
+
voiceAssistantMemoryTable.key
|
|
244
|
+
]
|
|
245
|
+
});
|
|
246
|
+
return record;
|
|
247
|
+
}
|
|
248
|
+
};
|
|
249
|
+
};
|
|
250
|
+
var createVoiceDrizzleAssistantMemoryStore = (options) => createDrizzleAssistantMemoryStore(options.db);
|
|
251
|
+
|
|
252
|
+
// src/drizzle/shared.ts
|
|
253
|
+
import { desc as desc2, eq as eq2 } from "drizzle-orm";
|
|
254
|
+
import {
|
|
255
|
+
bigint as bigint2,
|
|
256
|
+
jsonb as jsonb2,
|
|
257
|
+
pgTable as pgTable2,
|
|
258
|
+
text as text2
|
|
259
|
+
} from "drizzle-orm/pg-core";
|
|
260
|
+
var voiceDocumentTable = (name) => pgTable2(name, {
|
|
261
|
+
id: text2("id").primaryKey(),
|
|
262
|
+
payload: jsonb2("payload").notNull(),
|
|
263
|
+
sortAt: bigint2("sort_at", { mode: "number" }).notNull()
|
|
264
|
+
});
|
|
265
|
+
var createVoiceDrizzleRecordStore = (input) => {
|
|
266
|
+
const get = async (id) => {
|
|
267
|
+
const rows = await input.db.select({ payload: input.table.payload }).from(input.table).where(eq2(input.table.id, id)).limit(1);
|
|
268
|
+
return rows[0]?.payload;
|
|
269
|
+
};
|
|
270
|
+
const list = async () => {
|
|
271
|
+
const rows = await input.db.select({ payload: input.table.payload }).from(input.table).orderBy(desc2(input.table.sortAt), desc2(input.table.id));
|
|
272
|
+
return rows.map((row) => row.payload);
|
|
273
|
+
};
|
|
274
|
+
const set = async (id, value) => {
|
|
275
|
+
const decorated = input.decorate(id, value);
|
|
276
|
+
await input.db.insert(input.table).values({
|
|
277
|
+
id,
|
|
278
|
+
payload: decorated,
|
|
279
|
+
sortAt: input.getSortAt(decorated)
|
|
280
|
+
}).onConflictDoUpdate({
|
|
281
|
+
set: {
|
|
282
|
+
payload: decorated,
|
|
283
|
+
sortAt: input.getSortAt(decorated)
|
|
284
|
+
},
|
|
285
|
+
target: input.table.id
|
|
286
|
+
});
|
|
287
|
+
};
|
|
288
|
+
const remove = async (id) => {
|
|
289
|
+
await input.db.delete(input.table).where(eq2(input.table.id, id));
|
|
290
|
+
};
|
|
291
|
+
return { get, list, remove, set };
|
|
292
|
+
};
|
|
293
|
+
|
|
294
|
+
// src/drizzle/eval.ts
|
|
295
|
+
var voiceEvalBaselineTable = voiceDocumentTable("voice_eval_baseline");
|
|
296
|
+
var VOICE_EVAL_BASELINE_ID = "baseline";
|
|
297
|
+
var createDrizzleEvalBaselineStore = (db) => {
|
|
298
|
+
const store = createVoiceDrizzleRecordStore({
|
|
299
|
+
db,
|
|
300
|
+
decorate: (_id, value) => value,
|
|
301
|
+
getSortAt: (value) => value.checkedAt,
|
|
302
|
+
table: voiceEvalBaselineTable
|
|
303
|
+
});
|
|
304
|
+
return {
|
|
305
|
+
get: async () => store.get(VOICE_EVAL_BASELINE_ID),
|
|
306
|
+
set: async (report) => {
|
|
307
|
+
await store.set(VOICE_EVAL_BASELINE_ID, report);
|
|
308
|
+
}
|
|
309
|
+
};
|
|
310
|
+
};
|
|
311
|
+
var createVoiceDrizzleEvalBaselineStore = (options) => createDrizzleEvalBaselineStore(options.db);
|
|
312
|
+
|
|
313
|
+
// src/drizzle/handoff.ts
|
|
314
|
+
var voiceHandoffDeliveriesTable = voiceDocumentTable("voice_handoff_deliveries");
|
|
315
|
+
var createDrizzleHandoffDeliveryStore = (db) => createVoiceDrizzleRecordStore({
|
|
316
|
+
db,
|
|
317
|
+
decorate: (_id, value) => value,
|
|
318
|
+
getSortAt: (value) => value.createdAt,
|
|
319
|
+
table: voiceHandoffDeliveriesTable
|
|
320
|
+
});
|
|
321
|
+
var createVoiceDrizzleHandoffDeliveryStore = (options) => createDrizzleHandoffDeliveryStore(options.db);
|
|
322
|
+
|
|
323
|
+
// src/drizzle/observabilityExport.ts
|
|
324
|
+
var voiceObservabilityExportDeliveryReceiptsTable = voiceDocumentTable("voice_observability_export_receipts");
|
|
325
|
+
var createVoiceDrizzleObservabilityExportDeliveryReceiptStore = (options) => createVoiceDrizzleRecordStore({
|
|
326
|
+
db: options.db,
|
|
327
|
+
decorate: (id, value) => ({
|
|
328
|
+
...value,
|
|
329
|
+
id
|
|
330
|
+
}),
|
|
331
|
+
getSortAt: (value) => value.checkedAt,
|
|
332
|
+
table: voiceObservabilityExportDeliveryReceiptsTable
|
|
333
|
+
});
|
|
334
|
+
|
|
335
|
+
// src/drizzle/proofTrends.ts
|
|
336
|
+
var voiceRealCallProfileEvidenceTable = voiceDocumentTable("voice_real_call_profile_evidence");
|
|
337
|
+
var voiceRealCallProfileRecoveryJobsTable = voiceDocumentTable("voice_real_call_profile_recovery_jobs");
|
|
338
|
+
var parseRealCallProfileEvidenceBoundary = (value) => {
|
|
339
|
+
if (value === undefined) {
|
|
340
|
+
return;
|
|
341
|
+
}
|
|
342
|
+
if (value instanceof Date) {
|
|
343
|
+
return value.getTime();
|
|
344
|
+
}
|
|
345
|
+
if (typeof value === "number") {
|
|
346
|
+
return value;
|
|
347
|
+
}
|
|
348
|
+
return Date.parse(value);
|
|
349
|
+
};
|
|
350
|
+
var readRealCallProfileEvidenceSortTime = (evidence, fallback) => Date.parse(evidence.generatedAt ?? fallback) || Date.parse(fallback);
|
|
351
|
+
var matchesRealCallProfileEvidenceListOptions = (record, input) => {
|
|
352
|
+
const evidenceTime = readRealCallProfileEvidenceSortTime(record, record.createdAt);
|
|
353
|
+
const since = parseRealCallProfileEvidenceBoundary(input.since);
|
|
354
|
+
const until = parseRealCallProfileEvidenceBoundary(input.until);
|
|
355
|
+
return (!input.profileId || record.profileId === input.profileId) && (!input.sessionId || record.sessionId === input.sessionId) && (since === undefined || Number.isNaN(since) || evidenceTime >= since) && (until === undefined || Number.isNaN(until) || evidenceTime <= until);
|
|
356
|
+
};
|
|
357
|
+
var matchesRealCallProfileRecoveryJobListOptions = (job, input) => (!input.actionId || job.actionId === input.actionId) && (!input.status || job.status === input.status);
|
|
358
|
+
var createDrizzleRealCallProfileRecoveryJobStore = (db, options = {}) => {
|
|
359
|
+
const store = createVoiceDrizzleRecordStore({
|
|
360
|
+
db,
|
|
361
|
+
decorate: (_id, value) => value,
|
|
362
|
+
getSortAt: (value) => Date.parse(value.updatedAt) || Date.now(),
|
|
363
|
+
table: voiceRealCallProfileRecoveryJobsTable
|
|
364
|
+
});
|
|
365
|
+
const now = () => (options.now ?? (() => new Date))().toISOString();
|
|
366
|
+
const createId = () => `${options.idPrefix ?? "voice-recovery-job"}-${Date.now().toString(36)}-${Math.random().toString(36).slice(2, 10)}`;
|
|
367
|
+
return {
|
|
368
|
+
get: store.get,
|
|
369
|
+
create: async (input) => {
|
|
370
|
+
const createdAt = input.createdAt ?? now();
|
|
371
|
+
const job = {
|
|
372
|
+
actionId: input.actionId,
|
|
373
|
+
createdAt,
|
|
374
|
+
id: input.id ?? createId(),
|
|
375
|
+
message: input.message,
|
|
376
|
+
status: input.status ?? "queued",
|
|
377
|
+
updatedAt: createdAt
|
|
378
|
+
};
|
|
379
|
+
await store.set(job.id, job);
|
|
380
|
+
return job;
|
|
381
|
+
},
|
|
382
|
+
list: async (input = {}) => {
|
|
383
|
+
const limit = Number.isFinite(input.limit) && input.limit !== undefined && input.limit > 0 ? Math.floor(input.limit) : 50;
|
|
384
|
+
return (await store.list()).filter((job) => matchesRealCallProfileRecoveryJobListOptions(job, input)).slice(0, limit);
|
|
385
|
+
},
|
|
386
|
+
update: async (id, update) => {
|
|
387
|
+
const existing = await store.get(id);
|
|
388
|
+
if (!existing) {
|
|
389
|
+
return;
|
|
390
|
+
}
|
|
391
|
+
const next = {
|
|
392
|
+
...existing,
|
|
393
|
+
...update,
|
|
394
|
+
updatedAt: update.updatedAt ?? now()
|
|
395
|
+
};
|
|
396
|
+
await store.set(id, next);
|
|
397
|
+
return next;
|
|
398
|
+
}
|
|
399
|
+
};
|
|
400
|
+
};
|
|
401
|
+
var createDrizzleRealCallProfileEvidenceStore = (db, options = {}) => {
|
|
402
|
+
const store = createVoiceDrizzleRecordStore({
|
|
403
|
+
db,
|
|
404
|
+
decorate: (_id, value) => value,
|
|
405
|
+
getSortAt: (value) => readRealCallProfileEvidenceSortTime(value, value.createdAt),
|
|
406
|
+
table: voiceRealCallProfileEvidenceTable
|
|
407
|
+
});
|
|
408
|
+
const now = () => (options.now ?? (() => new Date))().toISOString();
|
|
409
|
+
const createId = () => `${options.idPrefix ?? "voice-profile-evidence"}-${Date.now().toString(36)}-${Math.random().toString(36).slice(2, 10)}`;
|
|
410
|
+
return {
|
|
411
|
+
get: store.get,
|
|
412
|
+
remove: store.remove,
|
|
413
|
+
append: async (input) => {
|
|
414
|
+
const record = {
|
|
415
|
+
...input,
|
|
416
|
+
createdAt: input.createdAt ?? now(),
|
|
417
|
+
id: input.id ?? createId()
|
|
418
|
+
};
|
|
419
|
+
await store.set(record.id, record);
|
|
420
|
+
return record;
|
|
421
|
+
},
|
|
422
|
+
list: async (input = {}) => {
|
|
423
|
+
const limit = Number.isFinite(input.limit) && input.limit !== undefined && input.limit > 0 ? Math.floor(input.limit) : 500;
|
|
424
|
+
return (await store.list()).filter((record) => matchesRealCallProfileEvidenceListOptions(record, input)).slice(0, limit);
|
|
425
|
+
}
|
|
426
|
+
};
|
|
427
|
+
};
|
|
428
|
+
var createVoiceDrizzleRealCallProfileEvidenceStore = (options) => createDrizzleRealCallProfileEvidenceStore(options.db, {
|
|
429
|
+
idPrefix: options.idPrefix,
|
|
430
|
+
now: options.now
|
|
431
|
+
});
|
|
432
|
+
var createVoiceDrizzleRealCallProfileRecoveryJobStore = (options) => createDrizzleRealCallProfileRecoveryJobStore(options.db, {
|
|
433
|
+
idPrefix: options.idPrefix,
|
|
434
|
+
now: options.now
|
|
435
|
+
});
|
|
436
|
+
|
|
437
|
+
// src/core/audit.ts
|
|
438
|
+
var includes = (filter, value) => {
|
|
439
|
+
if (!filter) {
|
|
440
|
+
return true;
|
|
441
|
+
}
|
|
442
|
+
if (!value) {
|
|
443
|
+
return false;
|
|
444
|
+
}
|
|
445
|
+
return Array.isArray(filter) ? filter.includes(value) : filter === value;
|
|
446
|
+
};
|
|
447
|
+
var createVoiceAuditEvent = (event) => ({
|
|
448
|
+
...event,
|
|
449
|
+
at: event.at ?? Date.now(),
|
|
450
|
+
id: event.id ?? crypto.randomUUID()
|
|
451
|
+
});
|
|
452
|
+
var createVoiceAuditLogger = (store) => ({
|
|
453
|
+
handoff: (input) => recordVoiceHandoffAuditEvent({ ...input, store }),
|
|
454
|
+
operatorAction: (input) => recordVoiceOperatorAuditEvent({ ...input, store }),
|
|
455
|
+
providerCall: (input) => recordVoiceProviderAuditEvent({ ...input, store }),
|
|
456
|
+
record: (event) => recordVoiceAuditEvent(store, event),
|
|
457
|
+
retention: (input) => recordVoiceRetentionAuditEvent({ ...input, store }),
|
|
458
|
+
toolCall: (input) => recordVoiceToolAuditEvent({ ...input, store })
|
|
459
|
+
});
|
|
460
|
+
var createVoiceMemoryAuditEventStore = () => {
|
|
461
|
+
const events = new Map;
|
|
462
|
+
return {
|
|
463
|
+
append: (event) => {
|
|
464
|
+
const stored = createVoiceAuditEvent(event);
|
|
465
|
+
events.set(stored.id, stored);
|
|
466
|
+
return stored;
|
|
467
|
+
},
|
|
468
|
+
get: (id) => events.get(id),
|
|
469
|
+
list: (filter) => filterVoiceAuditEvents([...events.values()], filter)
|
|
470
|
+
};
|
|
471
|
+
};
|
|
472
|
+
var createVoiceScopedAuditEventStore = (store, scope) => {
|
|
473
|
+
const upstreamFilter = (filter = {}) => {
|
|
474
|
+
const next = { ...filter };
|
|
475
|
+
delete next.limit;
|
|
476
|
+
if (scope.actorId !== undefined) {
|
|
477
|
+
delete next.actorId;
|
|
478
|
+
}
|
|
479
|
+
if (scope.outcome !== undefined) {
|
|
480
|
+
delete next.outcome;
|
|
481
|
+
}
|
|
482
|
+
if (scope.resourceId !== undefined) {
|
|
483
|
+
delete next.resourceId;
|
|
484
|
+
}
|
|
485
|
+
if (scope.resourceType !== undefined) {
|
|
486
|
+
delete next.resourceType;
|
|
487
|
+
}
|
|
488
|
+
if (scope.sessionId !== undefined) {
|
|
489
|
+
delete next.sessionId;
|
|
490
|
+
}
|
|
491
|
+
if (scope.traceId !== undefined) {
|
|
492
|
+
delete next.traceId;
|
|
493
|
+
}
|
|
494
|
+
if (scope.type !== undefined) {
|
|
495
|
+
delete next.type;
|
|
496
|
+
}
|
|
497
|
+
return next;
|
|
498
|
+
};
|
|
499
|
+
const scopedFilter = (filter = {}) => ({
|
|
500
|
+
...filter,
|
|
501
|
+
...scope
|
|
502
|
+
});
|
|
503
|
+
return {
|
|
504
|
+
append: (event) => store.append(event),
|
|
505
|
+
get: (id) => store.get(id),
|
|
506
|
+
list: async (filter) => filterVoiceAuditEvents(await store.list(upstreamFilter(filter)), scopedFilter(filter))
|
|
507
|
+
};
|
|
508
|
+
};
|
|
509
|
+
var filterVoiceAuditEvents = (events, filter = {}) => {
|
|
510
|
+
const sorted = events.filter((event) => {
|
|
511
|
+
if (!includes(filter.type, event.type)) {
|
|
512
|
+
return false;
|
|
513
|
+
}
|
|
514
|
+
if (!includes(filter.outcome, event.outcome)) {
|
|
515
|
+
return false;
|
|
516
|
+
}
|
|
517
|
+
if (filter.actorId && event.actor?.id !== filter.actorId) {
|
|
518
|
+
return false;
|
|
519
|
+
}
|
|
520
|
+
if (filter.resourceId && event.resource?.id !== filter.resourceId) {
|
|
521
|
+
return false;
|
|
522
|
+
}
|
|
523
|
+
if (filter.resourceType && event.resource?.type !== filter.resourceType) {
|
|
524
|
+
return false;
|
|
525
|
+
}
|
|
526
|
+
if (filter.sessionId && event.sessionId !== filter.sessionId) {
|
|
527
|
+
return false;
|
|
528
|
+
}
|
|
529
|
+
if (filter.traceId && event.traceId !== filter.traceId) {
|
|
530
|
+
return false;
|
|
531
|
+
}
|
|
532
|
+
if (typeof filter.after === "number" && event.at <= filter.after) {
|
|
533
|
+
return false;
|
|
534
|
+
}
|
|
535
|
+
if (typeof filter.afterOrAt === "number" && event.at < filter.afterOrAt) {
|
|
536
|
+
return false;
|
|
537
|
+
}
|
|
538
|
+
if (typeof filter.before === "number" && event.at >= filter.before) {
|
|
539
|
+
return false;
|
|
540
|
+
}
|
|
541
|
+
if (typeof filter.beforeOrAt === "number" && event.at > filter.beforeOrAt) {
|
|
542
|
+
return false;
|
|
543
|
+
}
|
|
544
|
+
return true;
|
|
545
|
+
}).sort((left, right) => left.at - right.at || left.id.localeCompare(right.id));
|
|
546
|
+
return typeof filter.limit === "number" && filter.limit >= 0 ? sorted.slice(0, filter.limit) : sorted;
|
|
547
|
+
};
|
|
548
|
+
var recordVoiceAuditEvent = (store, event) => store.append(createVoiceAuditEvent(event));
|
|
549
|
+
var recordVoiceHandoffAuditEvent = (input) => recordVoiceAuditEvent(input.store, {
|
|
550
|
+
action: "handoff",
|
|
551
|
+
actor: input.actor,
|
|
552
|
+
metadata: input.metadata,
|
|
553
|
+
outcome: input.outcome,
|
|
554
|
+
payload: {
|
|
555
|
+
fromAgentId: input.fromAgentId,
|
|
556
|
+
reason: input.reason,
|
|
557
|
+
target: input.target,
|
|
558
|
+
toAgentId: input.toAgentId
|
|
559
|
+
},
|
|
560
|
+
resource: {
|
|
561
|
+
id: input.toAgentId ?? input.target,
|
|
562
|
+
type: "handoff"
|
|
563
|
+
},
|
|
564
|
+
sessionId: input.sessionId,
|
|
565
|
+
traceId: input.traceId,
|
|
566
|
+
type: "handoff"
|
|
567
|
+
});
|
|
568
|
+
var recordVoiceOperatorAuditEvent = (input) => recordVoiceAuditEvent(input.store, {
|
|
569
|
+
action: input.action,
|
|
570
|
+
actor: input.actor,
|
|
571
|
+
metadata: input.metadata,
|
|
572
|
+
outcome: input.outcome ?? "success",
|
|
573
|
+
payload: input.payload,
|
|
574
|
+
resource: input.resource,
|
|
575
|
+
sessionId: input.sessionId,
|
|
576
|
+
traceId: input.traceId,
|
|
577
|
+
type: "operator.action"
|
|
578
|
+
});
|
|
579
|
+
var recordVoiceProviderAuditEvent = (input) => recordVoiceAuditEvent(input.store, {
|
|
580
|
+
action: `${input.kind}.provider.call`,
|
|
581
|
+
actor: input.actor,
|
|
582
|
+
metadata: input.metadata,
|
|
583
|
+
outcome: input.outcome,
|
|
584
|
+
payload: {
|
|
585
|
+
cost: input.cost,
|
|
586
|
+
elapsedMs: input.elapsedMs,
|
|
587
|
+
error: input.error,
|
|
588
|
+
kind: input.kind,
|
|
589
|
+
model: input.model,
|
|
590
|
+
provider: input.provider
|
|
591
|
+
},
|
|
592
|
+
resource: {
|
|
593
|
+
id: input.provider,
|
|
594
|
+
type: "provider"
|
|
595
|
+
},
|
|
596
|
+
sessionId: input.sessionId,
|
|
597
|
+
traceId: input.traceId,
|
|
598
|
+
type: "provider.call"
|
|
599
|
+
});
|
|
600
|
+
var recordVoiceRetentionAuditEvent = (input) => recordVoiceAuditEvent(input.store, {
|
|
601
|
+
action: input.dryRun ? "retention.plan" : "retention.apply",
|
|
602
|
+
actor: input.actor ?? {
|
|
603
|
+
id: "voice-retention",
|
|
604
|
+
kind: "system"
|
|
605
|
+
},
|
|
606
|
+
metadata: input.metadata,
|
|
607
|
+
outcome: "success",
|
|
608
|
+
payload: {
|
|
609
|
+
deletedCount: input.report.deletedCount,
|
|
610
|
+
dryRun: input.dryRun,
|
|
611
|
+
scopes: input.report.scopes
|
|
612
|
+
},
|
|
613
|
+
resource: {
|
|
614
|
+
type: "retention-policy"
|
|
615
|
+
},
|
|
616
|
+
type: "retention.policy"
|
|
617
|
+
});
|
|
618
|
+
var recordVoiceToolAuditEvent = (input) => recordVoiceAuditEvent(input.store, {
|
|
619
|
+
action: "tool.call",
|
|
620
|
+
actor: input.actor,
|
|
621
|
+
metadata: input.metadata,
|
|
622
|
+
outcome: input.outcome,
|
|
623
|
+
payload: {
|
|
624
|
+
elapsedMs: input.elapsedMs,
|
|
625
|
+
error: input.error,
|
|
626
|
+
toolCallId: input.toolCallId,
|
|
627
|
+
toolName: input.toolName
|
|
628
|
+
},
|
|
629
|
+
resource: {
|
|
630
|
+
id: input.toolName,
|
|
631
|
+
type: "tool"
|
|
632
|
+
},
|
|
633
|
+
sessionId: input.sessionId,
|
|
634
|
+
traceId: input.traceId,
|
|
635
|
+
type: "tool.call"
|
|
636
|
+
});
|
|
637
|
+
|
|
638
|
+
// src/core/ops.ts
|
|
639
|
+
var createVoiceExternalObjectMap = (input) => {
|
|
640
|
+
const at = input.at ?? Date.now();
|
|
641
|
+
return {
|
|
642
|
+
createdAt: at,
|
|
643
|
+
externalId: input.externalId,
|
|
644
|
+
id: createVoiceExternalObjectMapId(input),
|
|
645
|
+
provider: input.provider,
|
|
646
|
+
sinkId: input.sinkId,
|
|
647
|
+
sourceId: input.sourceId,
|
|
648
|
+
sourceType: input.sourceType,
|
|
649
|
+
updatedAt: at
|
|
650
|
+
};
|
|
651
|
+
};
|
|
652
|
+
var createVoiceExternalObjectMapId = (input) => [
|
|
653
|
+
input.provider,
|
|
654
|
+
input.sinkId ?? "default",
|
|
655
|
+
encodeURIComponent(input.sourceId)
|
|
656
|
+
].join(":");
|
|
657
|
+
var sleep = async (delayMs) => {
|
|
658
|
+
if (delayMs <= 0) {
|
|
659
|
+
return;
|
|
660
|
+
}
|
|
661
|
+
await new Promise((resolve) => setTimeout(resolve, delayMs));
|
|
662
|
+
};
|
|
663
|
+
var toHex = (bytes) => Array.from(bytes, (byte) => byte.toString(16).padStart(2, "0")).join("");
|
|
664
|
+
var signVoiceIntegrationWebhookBody = async (input) => {
|
|
665
|
+
const encoder = new TextEncoder;
|
|
666
|
+
const key = await crypto.subtle.importKey("raw", encoder.encode(input.secret), {
|
|
667
|
+
hash: "SHA-256",
|
|
668
|
+
name: "HMAC"
|
|
669
|
+
}, false, ["sign"]);
|
|
670
|
+
const payload = encoder.encode(`${input.timestamp}.${input.body}`);
|
|
671
|
+
const signature = await crypto.subtle.sign("HMAC", key, payload);
|
|
672
|
+
return `sha256=${toHex(new Uint8Array(signature))}`;
|
|
673
|
+
};
|
|
674
|
+
var createVoiceWebhookDeliveryError = (input) => {
|
|
675
|
+
if (input.response) {
|
|
676
|
+
const statusText = input.response.statusText?.trim();
|
|
677
|
+
return `Attempt ${input.attempt} failed with webhook response ${input.response.status}${statusText ? ` ${statusText}` : ""}.`;
|
|
678
|
+
}
|
|
679
|
+
if (input.error instanceof Error) {
|
|
680
|
+
return `Attempt ${input.attempt} failed: ${input.error.message}`;
|
|
681
|
+
}
|
|
682
|
+
return `Attempt ${input.attempt} failed: ${String(input.error)}`;
|
|
683
|
+
};
|
|
684
|
+
var deliverVoiceIntegrationEvent = async (input) => {
|
|
685
|
+
const previousAttempts = input.event.deliveryAttempts ?? 0;
|
|
686
|
+
const matchesConfiguredTypes = input.webhook.eventTypes === undefined ? true : input.webhook.eventTypes.includes(input.event.type);
|
|
687
|
+
if (!matchesConfiguredTypes) {
|
|
688
|
+
return {
|
|
689
|
+
...input.event,
|
|
690
|
+
deliveryAttempts: 0,
|
|
691
|
+
deliveryError: undefined,
|
|
692
|
+
deliveryStatus: "skipped"
|
|
693
|
+
};
|
|
694
|
+
}
|
|
695
|
+
const fetchImpl = input.webhook.fetch ?? globalThis.fetch;
|
|
696
|
+
if (typeof fetchImpl !== "function") {
|
|
697
|
+
return {
|
|
698
|
+
...input.event,
|
|
699
|
+
deliveredTo: input.webhook.url,
|
|
700
|
+
deliveryAttempts: 0,
|
|
701
|
+
deliveryError: "Webhook delivery failed: fetch is not available in this runtime.",
|
|
702
|
+
deliveryStatus: "failed"
|
|
703
|
+
};
|
|
704
|
+
}
|
|
705
|
+
const maxRetries = Math.max(0, input.webhook.retries ?? 0);
|
|
706
|
+
const backoffMs = Math.max(0, input.webhook.backoffMs ?? 250);
|
|
707
|
+
const timeoutMs = Math.max(0, input.webhook.timeoutMs ?? 1e4);
|
|
708
|
+
const body = JSON.stringify({
|
|
709
|
+
createdAt: input.event.createdAt,
|
|
710
|
+
id: input.event.id,
|
|
711
|
+
payload: input.event.payload,
|
|
712
|
+
type: input.event.type
|
|
713
|
+
});
|
|
714
|
+
let lastError = "Webhook delivery failed.";
|
|
715
|
+
for (let attempt = 1;attempt <= maxRetries + 1; attempt += 1) {
|
|
716
|
+
let controller;
|
|
717
|
+
let timeout;
|
|
718
|
+
try {
|
|
719
|
+
const headers = {
|
|
720
|
+
"content-type": "application/json",
|
|
721
|
+
...input.webhook.headers
|
|
722
|
+
};
|
|
723
|
+
if (input.webhook.signingSecret) {
|
|
724
|
+
const timestamp = String(Date.now());
|
|
725
|
+
headers["x-absolutejs-timestamp"] = timestamp;
|
|
726
|
+
headers["x-absolutejs-signature"] = await signVoiceIntegrationWebhookBody({
|
|
727
|
+
body,
|
|
728
|
+
secret: input.webhook.signingSecret,
|
|
729
|
+
timestamp
|
|
730
|
+
});
|
|
731
|
+
}
|
|
732
|
+
controller = timeoutMs > 0 ? new AbortController : undefined;
|
|
733
|
+
const activeController = controller;
|
|
734
|
+
timeout = activeController && timeoutMs > 0 ? setTimeout(() => activeController.abort(), timeoutMs) : undefined;
|
|
735
|
+
const response = await fetchImpl(input.webhook.url, {
|
|
736
|
+
body,
|
|
737
|
+
headers,
|
|
738
|
+
method: "POST",
|
|
739
|
+
signal: controller?.signal
|
|
740
|
+
});
|
|
741
|
+
if (response.ok) {
|
|
742
|
+
if (timeout) {
|
|
743
|
+
clearTimeout(timeout);
|
|
744
|
+
}
|
|
745
|
+
return {
|
|
746
|
+
...input.event,
|
|
747
|
+
deliveredAt: Date.now(),
|
|
748
|
+
deliveredTo: input.webhook.url,
|
|
749
|
+
deliveryAttempts: previousAttempts + attempt,
|
|
750
|
+
deliveryError: undefined,
|
|
751
|
+
deliveryStatus: "delivered"
|
|
752
|
+
};
|
|
753
|
+
}
|
|
754
|
+
lastError = createVoiceWebhookDeliveryError({
|
|
755
|
+
attempt,
|
|
756
|
+
error: new Error(`HTTP ${response.status}`),
|
|
757
|
+
response
|
|
758
|
+
});
|
|
759
|
+
} catch (error) {
|
|
760
|
+
lastError = createVoiceWebhookDeliveryError({
|
|
761
|
+
attempt,
|
|
762
|
+
error
|
|
763
|
+
});
|
|
764
|
+
} finally {
|
|
765
|
+
if (timeout) {
|
|
766
|
+
clearTimeout(timeout);
|
|
767
|
+
}
|
|
768
|
+
}
|
|
769
|
+
if (attempt <= maxRetries) {
|
|
770
|
+
await sleep(backoffMs * attempt);
|
|
771
|
+
}
|
|
772
|
+
}
|
|
773
|
+
return {
|
|
774
|
+
...input.event,
|
|
775
|
+
deliveredTo: input.webhook.url,
|
|
776
|
+
deliveryAttempts: previousAttempts + maxRetries + 1,
|
|
777
|
+
deliveryError: lastError,
|
|
778
|
+
deliveryStatus: "failed"
|
|
779
|
+
};
|
|
780
|
+
};
|
|
781
|
+
var ensureTaskHistory = (task, entry) => ({
|
|
782
|
+
...task,
|
|
783
|
+
history: [
|
|
784
|
+
...task.history ?? [],
|
|
785
|
+
{
|
|
786
|
+
...entry,
|
|
787
|
+
at: entry.at ?? Date.now()
|
|
788
|
+
}
|
|
789
|
+
],
|
|
790
|
+
updatedAt: Date.now()
|
|
791
|
+
});
|
|
792
|
+
var buildVoiceOpsTaskFromReview = (review) => {
|
|
793
|
+
const createdAt = review.generatedAt ?? Date.now();
|
|
794
|
+
const common = {
|
|
795
|
+
createdAt,
|
|
796
|
+
history: [
|
|
797
|
+
{
|
|
798
|
+
actor: "system",
|
|
799
|
+
at: createdAt,
|
|
800
|
+
detail: review.postCall?.summary,
|
|
801
|
+
type: "created"
|
|
802
|
+
}
|
|
803
|
+
],
|
|
804
|
+
id: `${review.id}:ops`,
|
|
805
|
+
intakeId: review.id,
|
|
806
|
+
outcome: review.summary.outcome,
|
|
807
|
+
recommendedAction: review.postCall?.recommendedAction ?? "Review the voice artifact and decide the next operator action.",
|
|
808
|
+
reviewId: review.id,
|
|
809
|
+
status: "open",
|
|
810
|
+
target: review.postCall?.target,
|
|
811
|
+
updatedAt: createdAt
|
|
812
|
+
};
|
|
813
|
+
switch (review.summary.outcome) {
|
|
814
|
+
case "voicemail":
|
|
815
|
+
return {
|
|
816
|
+
...common,
|
|
817
|
+
description: review.postCall?.summary ?? "Caller reached voicemail and needs a callback follow-up.",
|
|
818
|
+
kind: "callback",
|
|
819
|
+
title: review.postCall?.target ? `Call back voicemail from ${review.postCall.target}` : "Call back voicemail lead"
|
|
820
|
+
};
|
|
821
|
+
case "no-answer":
|
|
822
|
+
return {
|
|
823
|
+
...common,
|
|
824
|
+
description: review.postCall?.summary ?? "Live contact was not established and should be retried.",
|
|
825
|
+
kind: "callback",
|
|
826
|
+
title: "Retry no-answer call"
|
|
827
|
+
};
|
|
828
|
+
case "escalated":
|
|
829
|
+
return {
|
|
830
|
+
...common,
|
|
831
|
+
description: review.postCall?.summary ?? "The automated path escalated this call for human review.",
|
|
832
|
+
kind: "escalation",
|
|
833
|
+
title: "Review escalated call"
|
|
834
|
+
};
|
|
835
|
+
case "transferred":
|
|
836
|
+
return {
|
|
837
|
+
...common,
|
|
838
|
+
description: review.postCall?.summary ?? "The call was transferred and should be verified downstream.",
|
|
839
|
+
kind: "transfer-check",
|
|
840
|
+
title: review.postCall?.target ? `Verify transfer to ${review.postCall.target}` : "Verify call transfer"
|
|
841
|
+
};
|
|
842
|
+
case "failed":
|
|
843
|
+
return {
|
|
844
|
+
...common,
|
|
845
|
+
description: review.postCall?.summary ?? "The call failed and needs operator review before retry.",
|
|
846
|
+
kind: "retry-review",
|
|
847
|
+
title: "Inspect failed call before retry"
|
|
848
|
+
};
|
|
849
|
+
default:
|
|
850
|
+
return null;
|
|
851
|
+
}
|
|
852
|
+
};
|
|
853
|
+
var withVoiceIntegrationEventId = (id, event) => ({
|
|
854
|
+
...event,
|
|
855
|
+
id
|
|
856
|
+
});
|
|
857
|
+
var withVoiceOpsTaskId = (id, task) => ({
|
|
858
|
+
...task,
|
|
859
|
+
id
|
|
860
|
+
});
|
|
861
|
+
var DEFAULT_VOICE_OPS_TASK_POLICIES = {
|
|
862
|
+
escalated: {
|
|
863
|
+
dueInMs: 10 * 60000,
|
|
864
|
+
name: "escalation-rapid-response",
|
|
865
|
+
priority: "urgent"
|
|
866
|
+
},
|
|
867
|
+
failed: {
|
|
868
|
+
dueInMs: 15 * 60000,
|
|
869
|
+
name: "failed-call-review",
|
|
870
|
+
priority: "high"
|
|
871
|
+
},
|
|
872
|
+
"no-answer": {
|
|
873
|
+
dueInMs: 2 * 60 * 60000,
|
|
874
|
+
name: "no-answer-retry",
|
|
875
|
+
priority: "normal"
|
|
876
|
+
},
|
|
877
|
+
transferred: {
|
|
878
|
+
dueInMs: 20 * 60000,
|
|
879
|
+
name: "transfer-verification",
|
|
880
|
+
priority: "normal"
|
|
881
|
+
},
|
|
882
|
+
voicemail: {
|
|
883
|
+
dueInMs: 30 * 60000,
|
|
884
|
+
name: "voicemail-callback",
|
|
885
|
+
priority: "high"
|
|
886
|
+
}
|
|
887
|
+
};
|
|
888
|
+
var applyVoiceOpsTaskAssignmentRule = (task, rule, input = {}) => {
|
|
889
|
+
const updatedTask = {
|
|
890
|
+
...task,
|
|
891
|
+
assignee: rule.assign ?? task.assignee,
|
|
892
|
+
priority: rule.priority ?? task.priority,
|
|
893
|
+
queue: rule.queue ?? task.queue,
|
|
894
|
+
recommendedAction: rule.recommendedAction ?? task.recommendedAction,
|
|
895
|
+
title: rule.title ?? task.title
|
|
896
|
+
};
|
|
897
|
+
return ensureTaskHistory(updatedTask, {
|
|
898
|
+
actor: input.actor ?? "system",
|
|
899
|
+
at: input.at,
|
|
900
|
+
detail: input.detail ?? rule.description ?? (rule.name ? `Applied assignment rule ${rule.name}` : "Applied assignment rule"),
|
|
901
|
+
type: "assigned"
|
|
902
|
+
});
|
|
903
|
+
};
|
|
904
|
+
var applyVoiceOpsTaskPolicy = (task, policy, input = {}) => {
|
|
905
|
+
const at = input.at ?? Date.now();
|
|
906
|
+
const updatedTask = {
|
|
907
|
+
...task,
|
|
908
|
+
assignee: policy.assignee ?? task.assignee,
|
|
909
|
+
dueAt: typeof policy.dueInMs === "number" ? at + Math.max(0, policy.dueInMs) : task.dueAt,
|
|
910
|
+
policyName: policy.name ?? task.policyName,
|
|
911
|
+
priority: policy.priority ?? task.priority,
|
|
912
|
+
queue: policy.queue ?? task.queue,
|
|
913
|
+
recommendedAction: policy.recommendedAction ?? task.recommendedAction,
|
|
914
|
+
target: policy.target ?? task.target,
|
|
915
|
+
title: policy.title ?? task.title
|
|
916
|
+
};
|
|
917
|
+
return ensureTaskHistory(updatedTask, {
|
|
918
|
+
actor: input.actor ?? "system",
|
|
919
|
+
at,
|
|
920
|
+
detail: input.detail ?? (policy.name ? `Applied ops policy ${policy.name}` : "Applied ops task policy"),
|
|
921
|
+
type: "policy-applied"
|
|
922
|
+
});
|
|
923
|
+
};
|
|
924
|
+
var assignVoiceOpsTask = (task, owner, input = {}) => {
|
|
925
|
+
const normalizedOwner = owner.trim() || "ops";
|
|
926
|
+
return ensureTaskHistory({
|
|
927
|
+
...task,
|
|
928
|
+
assignee: normalizedOwner
|
|
929
|
+
}, {
|
|
930
|
+
actor: input.actor ?? normalizedOwner,
|
|
931
|
+
at: input.at,
|
|
932
|
+
detail: `Assigned to ${normalizedOwner}`,
|
|
933
|
+
type: "assigned"
|
|
934
|
+
});
|
|
935
|
+
};
|
|
936
|
+
var buildVoiceOpsTaskFromSLABreach = (task, policy = {}) => {
|
|
937
|
+
const createdAt = task.slaBreachedAt ?? Date.now();
|
|
938
|
+
const followUp = withVoiceOpsTaskId(`${task.id}:sla`, {
|
|
939
|
+
assignee: policy.assignee ?? task.assignee,
|
|
940
|
+
createdAt,
|
|
941
|
+
description: policy.description ?? `Task ${task.id} breached its SLA and needs operator follow-up.`,
|
|
942
|
+
dueAt: typeof policy.dueInMs === "number" ? createdAt + Math.max(0, policy.dueInMs) : undefined,
|
|
943
|
+
history: [
|
|
944
|
+
{
|
|
945
|
+
actor: "system",
|
|
946
|
+
at: createdAt,
|
|
947
|
+
detail: policy.name ?? (task.policyName ? `Created from SLA breach on policy ${task.policyName}` : "Created from SLA breach"),
|
|
948
|
+
type: "created"
|
|
949
|
+
}
|
|
950
|
+
],
|
|
951
|
+
kind: task.kind,
|
|
952
|
+
intakeId: task.intakeId,
|
|
953
|
+
outcome: task.outcome,
|
|
954
|
+
priority: policy.priority ?? "urgent",
|
|
955
|
+
policyName: policy.name ?? `${task.policyName ?? task.kind}:sla`,
|
|
956
|
+
queue: policy.queue ?? task.queue,
|
|
957
|
+
recommendedAction: policy.recommendedAction ?? `Review overdue task ${task.id} and decide the next operator action.`,
|
|
958
|
+
reviewId: task.reviewId,
|
|
959
|
+
status: "open",
|
|
960
|
+
target: task.target,
|
|
961
|
+
title: policy.title ?? `SLA follow-up for ${task.title}`,
|
|
962
|
+
updatedAt: createdAt
|
|
963
|
+
});
|
|
964
|
+
return followUp;
|
|
965
|
+
};
|
|
966
|
+
var claimVoiceOpsTask = (task, workerId, input = {
|
|
967
|
+
leaseMs: 30000
|
|
968
|
+
}) => {
|
|
969
|
+
const at = input.at ?? Date.now();
|
|
970
|
+
const leaseMs = Math.max(1, input.leaseMs);
|
|
971
|
+
return ensureTaskHistory({
|
|
972
|
+
...task,
|
|
973
|
+
claimExpiresAt: at + leaseMs,
|
|
974
|
+
claimedAt: at,
|
|
975
|
+
claimedBy: workerId,
|
|
976
|
+
status: task.status === "done" ? task.status : "in-progress"
|
|
977
|
+
}, {
|
|
978
|
+
actor: input.actor ?? workerId,
|
|
979
|
+
at,
|
|
980
|
+
detail: input.detail ?? `Claimed by ${workerId}`,
|
|
981
|
+
type: "claimed"
|
|
982
|
+
});
|
|
983
|
+
};
|
|
984
|
+
var completeVoiceOpsTask = (task, input = {}) => ensureTaskHistory({
|
|
985
|
+
...task,
|
|
986
|
+
claimExpiresAt: undefined,
|
|
987
|
+
claimedAt: undefined,
|
|
988
|
+
claimedBy: undefined,
|
|
989
|
+
lastProcessedAt: input.at ?? Date.now(),
|
|
990
|
+
processingError: undefined,
|
|
991
|
+
status: "done"
|
|
992
|
+
}, {
|
|
993
|
+
actor: input.actor ?? task.assignee ?? "ops",
|
|
994
|
+
at: input.at,
|
|
995
|
+
detail: input.detail ?? "Marked done",
|
|
996
|
+
type: "completed"
|
|
997
|
+
});
|
|
998
|
+
var createVoiceCallCompletedEvent = (input) => createVoiceIntegrationEvent("call.completed", {
|
|
999
|
+
call: input.session.call,
|
|
1000
|
+
disposition: input.disposition ?? input.session.call?.disposition,
|
|
1001
|
+
scenarioId: input.session.scenarioId,
|
|
1002
|
+
sessionId: input.session.id,
|
|
1003
|
+
sessionSummary: input.sessionSummary,
|
|
1004
|
+
status: input.session.status,
|
|
1005
|
+
turnCount: input.session.turns.length
|
|
1006
|
+
}, {
|
|
1007
|
+
id: `${input.session.id}:call.completed`
|
|
1008
|
+
});
|
|
1009
|
+
var createVoiceIntegrationEvent = (type, payload, input = {}) => ({
|
|
1010
|
+
createdAt: input.createdAt ?? Date.now(),
|
|
1011
|
+
id: input.id ?? crypto.randomUUID(),
|
|
1012
|
+
payload,
|
|
1013
|
+
type
|
|
1014
|
+
});
|
|
1015
|
+
var createVoiceReviewSavedEvent = (review) => createVoiceIntegrationEvent("review.saved", {
|
|
1016
|
+
elapsedMs: review.summary.elapsedMs,
|
|
1017
|
+
firstTurnLatencyMs: review.summary.firstTurnLatencyMs,
|
|
1018
|
+
outcome: review.summary.outcome,
|
|
1019
|
+
postCall: review.postCall,
|
|
1020
|
+
reviewId: review.id,
|
|
1021
|
+
title: review.title
|
|
1022
|
+
}, {
|
|
1023
|
+
id: `${review.id}:review.saved`
|
|
1024
|
+
});
|
|
1025
|
+
var createVoiceTaskCreatedEvent = (task) => createVoiceIntegrationEvent("task.created", {
|
|
1026
|
+
assignee: task.assignee,
|
|
1027
|
+
dueAt: task.dueAt,
|
|
1028
|
+
kind: task.kind,
|
|
1029
|
+
outcome: task.outcome,
|
|
1030
|
+
priority: task.priority,
|
|
1031
|
+
queue: task.queue,
|
|
1032
|
+
recommendedAction: task.recommendedAction,
|
|
1033
|
+
reviewId: task.reviewId,
|
|
1034
|
+
status: task.status,
|
|
1035
|
+
target: task.target,
|
|
1036
|
+
taskId: task.id,
|
|
1037
|
+
title: task.title
|
|
1038
|
+
}, {
|
|
1039
|
+
id: `${task.id}:task.created:${task.updatedAt}`
|
|
1040
|
+
});
|
|
1041
|
+
var createVoiceTaskSLABreachedEvent = (task) => createVoiceIntegrationEvent("task.sla_breached", {
|
|
1042
|
+
assignee: task.assignee,
|
|
1043
|
+
dueAt: task.dueAt,
|
|
1044
|
+
kind: task.kind,
|
|
1045
|
+
outcome: task.outcome,
|
|
1046
|
+
priority: task.priority,
|
|
1047
|
+
queue: task.queue,
|
|
1048
|
+
recommendedAction: task.recommendedAction,
|
|
1049
|
+
reviewId: task.reviewId,
|
|
1050
|
+
slaBreachedAt: task.slaBreachedAt,
|
|
1051
|
+
status: task.status,
|
|
1052
|
+
target: task.target,
|
|
1053
|
+
taskId: task.id,
|
|
1054
|
+
title: task.title
|
|
1055
|
+
}, {
|
|
1056
|
+
id: `${task.id}:task.sla_breached:${task.slaBreachedAt ?? task.updatedAt}`
|
|
1057
|
+
});
|
|
1058
|
+
var createVoiceTaskUpdatedEvent = (task) => createVoiceIntegrationEvent("task.updated", {
|
|
1059
|
+
assignee: task.assignee,
|
|
1060
|
+
dueAt: task.dueAt,
|
|
1061
|
+
history: task.history,
|
|
1062
|
+
kind: task.kind,
|
|
1063
|
+
outcome: task.outcome,
|
|
1064
|
+
priority: task.priority,
|
|
1065
|
+
queue: task.queue,
|
|
1066
|
+
recommendedAction: task.recommendedAction,
|
|
1067
|
+
reviewId: task.reviewId,
|
|
1068
|
+
slaBreachedAt: task.slaBreachedAt,
|
|
1069
|
+
status: task.status,
|
|
1070
|
+
target: task.target,
|
|
1071
|
+
taskId: task.id,
|
|
1072
|
+
title: task.title,
|
|
1073
|
+
updatedAt: task.updatedAt
|
|
1074
|
+
}, {
|
|
1075
|
+
id: `${task.id}:task.updated:${task.updatedAt}`
|
|
1076
|
+
});
|
|
1077
|
+
var deadLetterVoiceOpsTask = (task, input = {}) => {
|
|
1078
|
+
const at = input.at ?? Date.now();
|
|
1079
|
+
return ensureTaskHistory({
|
|
1080
|
+
...task,
|
|
1081
|
+
claimExpiresAt: undefined,
|
|
1082
|
+
claimedAt: undefined,
|
|
1083
|
+
claimedBy: undefined,
|
|
1084
|
+
deadLetteredAt: at,
|
|
1085
|
+
lastProcessedAt: at,
|
|
1086
|
+
status: "open"
|
|
1087
|
+
}, {
|
|
1088
|
+
actor: input.actor ?? task.assignee ?? "ops",
|
|
1089
|
+
at,
|
|
1090
|
+
detail: input.detail ?? "Task moved to dead-letter queue",
|
|
1091
|
+
type: "dead-lettered"
|
|
1092
|
+
});
|
|
1093
|
+
};
|
|
1094
|
+
var failVoiceOpsTask = (task, input = {}) => {
|
|
1095
|
+
const at = input.at ?? Date.now();
|
|
1096
|
+
const detail = input.detail ?? input.error ?? "Task processing failed";
|
|
1097
|
+
return ensureTaskHistory({
|
|
1098
|
+
...task,
|
|
1099
|
+
lastProcessedAt: at,
|
|
1100
|
+
processingAttempts: (task.processingAttempts ?? 0) + 1,
|
|
1101
|
+
processingError: input.error ?? detail,
|
|
1102
|
+
status: task.status === "done" ? task.status : "open"
|
|
1103
|
+
}, {
|
|
1104
|
+
actor: input.actor ?? task.claimedBy ?? task.assignee ?? "ops",
|
|
1105
|
+
at,
|
|
1106
|
+
detail,
|
|
1107
|
+
type: "failed"
|
|
1108
|
+
});
|
|
1109
|
+
};
|
|
1110
|
+
var hasVoiceOpsTaskSLABreach = (task) => typeof task.slaBreachedAt === "number";
|
|
1111
|
+
var heartbeatVoiceOpsTask = (task, workerId, input = {
|
|
1112
|
+
leaseMs: 30000
|
|
1113
|
+
}) => {
|
|
1114
|
+
if (task.claimedBy && task.claimedBy !== workerId) {
|
|
1115
|
+
throw new Error(`Cannot heartbeat task ${task.id}: claimed by ${task.claimedBy}, not ${workerId}.`);
|
|
1116
|
+
}
|
|
1117
|
+
const at = input.at ?? Date.now();
|
|
1118
|
+
const leaseMs = Math.max(1, input.leaseMs);
|
|
1119
|
+
return ensureTaskHistory({
|
|
1120
|
+
...task,
|
|
1121
|
+
claimExpiresAt: at + leaseMs,
|
|
1122
|
+
claimedAt: task.claimedAt ?? at,
|
|
1123
|
+
claimedBy: workerId
|
|
1124
|
+
}, {
|
|
1125
|
+
actor: input.actor ?? workerId,
|
|
1126
|
+
at,
|
|
1127
|
+
detail: input.detail ?? `Heartbeat from ${workerId}`,
|
|
1128
|
+
type: "heartbeat"
|
|
1129
|
+
});
|
|
1130
|
+
};
|
|
1131
|
+
var isVoiceOpsTaskOverdue = (task, input = {}) => typeof task.dueAt === "number" && task.status !== "done" && task.dueAt <= (input.at ?? Date.now());
|
|
1132
|
+
var listVoiceOpsTasks = (tasks) => [...tasks].sort((left, right) => right.createdAt - left.createdAt);
|
|
1133
|
+
var markVoiceOpsTaskSLABreached = (task, input = {}) => {
|
|
1134
|
+
const at = input.at ?? Date.now();
|
|
1135
|
+
if (hasVoiceOpsTaskSLABreach(task)) {
|
|
1136
|
+
return task;
|
|
1137
|
+
}
|
|
1138
|
+
return ensureTaskHistory({
|
|
1139
|
+
...task,
|
|
1140
|
+
slaBreachedAt: at
|
|
1141
|
+
}, {
|
|
1142
|
+
actor: input.actor ?? "system",
|
|
1143
|
+
at,
|
|
1144
|
+
detail: input.detail ?? "Task breached its SLA",
|
|
1145
|
+
type: "sla-breached"
|
|
1146
|
+
});
|
|
1147
|
+
};
|
|
1148
|
+
var matchesVoiceOpsTaskAssignmentRule = (task, rule) => {
|
|
1149
|
+
const { when } = rule;
|
|
1150
|
+
if (!when) {
|
|
1151
|
+
return true;
|
|
1152
|
+
}
|
|
1153
|
+
if (when.assignee !== undefined && task.assignee !== when.assignee) {
|
|
1154
|
+
return false;
|
|
1155
|
+
}
|
|
1156
|
+
if (when.kind !== undefined && task.kind !== when.kind) {
|
|
1157
|
+
return false;
|
|
1158
|
+
}
|
|
1159
|
+
if (when.outcome !== undefined && task.outcome !== when.outcome) {
|
|
1160
|
+
return false;
|
|
1161
|
+
}
|
|
1162
|
+
if (when.policyName !== undefined && task.policyName !== when.policyName) {
|
|
1163
|
+
return false;
|
|
1164
|
+
}
|
|
1165
|
+
if (when.priority !== undefined && task.priority !== when.priority) {
|
|
1166
|
+
return false;
|
|
1167
|
+
}
|
|
1168
|
+
if (when.queue !== undefined && task.queue !== when.queue) {
|
|
1169
|
+
return false;
|
|
1170
|
+
}
|
|
1171
|
+
if (when.status !== undefined && task.status !== when.status) {
|
|
1172
|
+
return false;
|
|
1173
|
+
}
|
|
1174
|
+
return true;
|
|
1175
|
+
};
|
|
1176
|
+
var reopenVoiceOpsTask = (task, input = {}) => ensureTaskHistory({
|
|
1177
|
+
...task,
|
|
1178
|
+
claimExpiresAt: undefined,
|
|
1179
|
+
claimedAt: undefined,
|
|
1180
|
+
claimedBy: undefined,
|
|
1181
|
+
deadLetteredAt: undefined,
|
|
1182
|
+
processingError: undefined,
|
|
1183
|
+
status: "open"
|
|
1184
|
+
}, {
|
|
1185
|
+
actor: input.actor ?? task.assignee ?? "ops",
|
|
1186
|
+
at: input.at,
|
|
1187
|
+
detail: input.detail ?? "Task reopened",
|
|
1188
|
+
type: "reopened"
|
|
1189
|
+
});
|
|
1190
|
+
var requeueVoiceOpsTask = (task, input = {}) => ensureTaskHistory({
|
|
1191
|
+
...task,
|
|
1192
|
+
claimExpiresAt: undefined,
|
|
1193
|
+
claimedAt: undefined,
|
|
1194
|
+
claimedBy: undefined,
|
|
1195
|
+
processingError: undefined,
|
|
1196
|
+
status: "open"
|
|
1197
|
+
}, {
|
|
1198
|
+
actor: input.actor ?? task.claimedBy ?? task.assignee ?? "ops",
|
|
1199
|
+
at: input.at,
|
|
1200
|
+
detail: input.detail ?? "Task requeued",
|
|
1201
|
+
type: "requeued"
|
|
1202
|
+
});
|
|
1203
|
+
var resolveVoiceOpsTaskAgeBucket = (task, input = {}) => {
|
|
1204
|
+
const at = input.at ?? Date.now();
|
|
1205
|
+
const freshMs = Math.max(0, input.agingMs ?? 30 * 60000);
|
|
1206
|
+
const staleMs = Math.max(freshMs, input.staleMs ?? 4 * 60 * 60000);
|
|
1207
|
+
const dueSoonMs = Math.max(0, input.dueSoonMs ?? 15 * 60000);
|
|
1208
|
+
const ageMs = Math.max(0, at - task.createdAt);
|
|
1209
|
+
if (isVoiceOpsTaskOverdue(task, { at })) {
|
|
1210
|
+
return ageMs >= staleMs ? "stale" : "overdue";
|
|
1211
|
+
}
|
|
1212
|
+
if (typeof task.dueAt === "number" && task.status !== "done" && task.dueAt - at <= dueSoonMs) {
|
|
1213
|
+
return "due-soon";
|
|
1214
|
+
}
|
|
1215
|
+
if (ageMs >= staleMs) {
|
|
1216
|
+
return "stale";
|
|
1217
|
+
}
|
|
1218
|
+
if (ageMs >= freshMs) {
|
|
1219
|
+
return "aging";
|
|
1220
|
+
}
|
|
1221
|
+
return "fresh";
|
|
1222
|
+
};
|
|
1223
|
+
var resolveVoiceOpsTaskAssignment = (input) => input.rules?.find((rule) => matchesVoiceOpsTaskAssignmentRule(input.task, rule));
|
|
1224
|
+
var resolveVoiceOpsTaskPolicy = (input) => {
|
|
1225
|
+
const { disposition } = input;
|
|
1226
|
+
if (!disposition) {
|
|
1227
|
+
return;
|
|
1228
|
+
}
|
|
1229
|
+
const defaultPolicy = DEFAULT_VOICE_OPS_TASK_POLICIES[disposition];
|
|
1230
|
+
const customPolicy = input.policies?.[disposition];
|
|
1231
|
+
if (!defaultPolicy && !customPolicy) {
|
|
1232
|
+
return;
|
|
1233
|
+
}
|
|
1234
|
+
return {
|
|
1235
|
+
...defaultPolicy,
|
|
1236
|
+
...customPolicy
|
|
1237
|
+
};
|
|
1238
|
+
};
|
|
1239
|
+
var startVoiceOpsTask = (task, input = {}) => ensureTaskHistory({
|
|
1240
|
+
...task,
|
|
1241
|
+
status: "in-progress"
|
|
1242
|
+
}, {
|
|
1243
|
+
actor: input.actor ?? task.assignee ?? "ops",
|
|
1244
|
+
at: input.at,
|
|
1245
|
+
detail: input.detail ?? "Work started",
|
|
1246
|
+
type: "started"
|
|
1247
|
+
});
|
|
1248
|
+
var summarizeVoiceOpsTaskAnalytics = (tasks, input = {}) => {
|
|
1249
|
+
const at = input.at ?? Date.now();
|
|
1250
|
+
const agingBuckets = new Map;
|
|
1251
|
+
const assignees = new Map;
|
|
1252
|
+
const workers = new Map;
|
|
1253
|
+
let totalCompleted = 0;
|
|
1254
|
+
let totalOverdue = 0;
|
|
1255
|
+
for (const task of tasks) {
|
|
1256
|
+
const bucket = resolveVoiceOpsTaskAgeBucket(task, { ...input, at });
|
|
1257
|
+
agingBuckets.set(bucket, (agingBuckets.get(bucket) ?? 0) + 1);
|
|
1258
|
+
if (task.assignee) {
|
|
1259
|
+
const assignee = assignees.get(task.assignee) ?? {
|
|
1260
|
+
claimed: 0,
|
|
1261
|
+
completed: 0,
|
|
1262
|
+
completionMsTotal: 0,
|
|
1263
|
+
completionSamples: 0,
|
|
1264
|
+
inProgress: 0,
|
|
1265
|
+
open: 0,
|
|
1266
|
+
overdue: 0,
|
|
1267
|
+
total: 0
|
|
1268
|
+
};
|
|
1269
|
+
assignee.total += 1;
|
|
1270
|
+
if (task.status === "open") {
|
|
1271
|
+
assignee.open += 1;
|
|
1272
|
+
} else if (task.status === "in-progress") {
|
|
1273
|
+
assignee.inProgress += 1;
|
|
1274
|
+
} else if (task.status === "done") {
|
|
1275
|
+
assignee.completed += 1;
|
|
1276
|
+
}
|
|
1277
|
+
if (task.claimedBy && (!task.claimExpiresAt || task.claimExpiresAt > at)) {
|
|
1278
|
+
assignee.claimed += 1;
|
|
1279
|
+
}
|
|
1280
|
+
if (isVoiceOpsTaskOverdue(task, { at })) {
|
|
1281
|
+
assignee.overdue += 1;
|
|
1282
|
+
}
|
|
1283
|
+
const completedAt = task.history.findLast((entry) => entry.type === "completed")?.at;
|
|
1284
|
+
if (task.status === "done" && typeof completedAt === "number") {
|
|
1285
|
+
assignee.completionMsTotal += Math.max(0, completedAt - task.createdAt);
|
|
1286
|
+
assignee.completionSamples += 1;
|
|
1287
|
+
}
|
|
1288
|
+
assignees.set(task.assignee, assignee);
|
|
1289
|
+
}
|
|
1290
|
+
if (isVoiceOpsTaskOverdue(task, { at })) {
|
|
1291
|
+
totalOverdue += 1;
|
|
1292
|
+
}
|
|
1293
|
+
if (task.status === "done") {
|
|
1294
|
+
totalCompleted += 1;
|
|
1295
|
+
}
|
|
1296
|
+
for (const entry of task.history) {
|
|
1297
|
+
if (!["claimed", "heartbeat", "completed", "failed", "requeued"].includes(entry.type)) {
|
|
1298
|
+
continue;
|
|
1299
|
+
}
|
|
1300
|
+
const worker = workers.get(entry.actor) ?? {
|
|
1301
|
+
activeClaims: 0,
|
|
1302
|
+
completed: 0,
|
|
1303
|
+
failed: 0,
|
|
1304
|
+
heartbeats: 0,
|
|
1305
|
+
requeued: 0,
|
|
1306
|
+
totalClaims: 0
|
|
1307
|
+
};
|
|
1308
|
+
if (entry.type === "claimed") {
|
|
1309
|
+
worker.totalClaims += 1;
|
|
1310
|
+
} else if (entry.type === "heartbeat") {
|
|
1311
|
+
worker.heartbeats += 1;
|
|
1312
|
+
} else if (entry.type === "completed") {
|
|
1313
|
+
worker.completed += 1;
|
|
1314
|
+
} else if (entry.type === "failed") {
|
|
1315
|
+
worker.failed += 1;
|
|
1316
|
+
} else if (entry.type === "requeued") {
|
|
1317
|
+
worker.requeued += 1;
|
|
1318
|
+
}
|
|
1319
|
+
workers.set(entry.actor, worker);
|
|
1320
|
+
}
|
|
1321
|
+
if (task.claimedBy && (!task.claimExpiresAt || task.claimExpiresAt > at)) {
|
|
1322
|
+
const worker = workers.get(task.claimedBy) ?? {
|
|
1323
|
+
activeClaims: 0,
|
|
1324
|
+
completed: 0,
|
|
1325
|
+
failed: 0,
|
|
1326
|
+
heartbeats: 0,
|
|
1327
|
+
requeued: 0,
|
|
1328
|
+
totalClaims: 0
|
|
1329
|
+
};
|
|
1330
|
+
worker.activeClaims += 1;
|
|
1331
|
+
workers.set(task.claimedBy, worker);
|
|
1332
|
+
}
|
|
1333
|
+
}
|
|
1334
|
+
return {
|
|
1335
|
+
agingBuckets: [...agingBuckets.entries()].sort((left, right) => right[1] - left[1]),
|
|
1336
|
+
assignees: [...assignees.entries()].map(([assignee, value]) => ({
|
|
1337
|
+
assignee,
|
|
1338
|
+
averageCompletionMs: value.completionSamples > 0 ? value.completionMsTotal / value.completionSamples : undefined,
|
|
1339
|
+
claimed: value.claimed,
|
|
1340
|
+
completed: value.completed,
|
|
1341
|
+
inProgress: value.inProgress,
|
|
1342
|
+
open: value.open,
|
|
1343
|
+
overdue: value.overdue,
|
|
1344
|
+
total: value.total
|
|
1345
|
+
})).sort((left, right) => right.total - left.total),
|
|
1346
|
+
totalCompleted,
|
|
1347
|
+
totalOverdue,
|
|
1348
|
+
totalTasks: tasks.length,
|
|
1349
|
+
workers: [...workers.entries()].map(([workerId, value]) => ({
|
|
1350
|
+
activeClaims: value.activeClaims,
|
|
1351
|
+
completed: value.completed,
|
|
1352
|
+
failed: value.failed,
|
|
1353
|
+
heartbeats: value.heartbeats,
|
|
1354
|
+
requeued: value.requeued,
|
|
1355
|
+
totalClaims: value.totalClaims,
|
|
1356
|
+
workerId
|
|
1357
|
+
})).sort((left, right) => right.totalClaims - left.totalClaims)
|
|
1358
|
+
};
|
|
1359
|
+
};
|
|
1360
|
+
var summarizeVoiceOpsTasks = (tasks) => {
|
|
1361
|
+
const summary = {
|
|
1362
|
+
byClaimedBy: new Map,
|
|
1363
|
+
byKind: new Map,
|
|
1364
|
+
byOutcome: new Map,
|
|
1365
|
+
byPriority: new Map,
|
|
1366
|
+
byQueue: new Map,
|
|
1367
|
+
claimed: 0,
|
|
1368
|
+
done: 0,
|
|
1369
|
+
inProgress: 0,
|
|
1370
|
+
open: 0,
|
|
1371
|
+
overdue: 0,
|
|
1372
|
+
topAssignees: new Map,
|
|
1373
|
+
topQueues: new Map,
|
|
1374
|
+
topTargets: new Map,
|
|
1375
|
+
total: tasks.length
|
|
1376
|
+
};
|
|
1377
|
+
for (const task of tasks) {
|
|
1378
|
+
if (task.status === "open") {
|
|
1379
|
+
summary.open += 1;
|
|
1380
|
+
} else if (task.status === "in-progress") {
|
|
1381
|
+
summary.inProgress += 1;
|
|
1382
|
+
} else if (task.status === "done") {
|
|
1383
|
+
summary.done += 1;
|
|
1384
|
+
}
|
|
1385
|
+
if (task.claimedBy && (!task.claimExpiresAt || task.claimExpiresAt > Date.now())) {
|
|
1386
|
+
summary.claimed += 1;
|
|
1387
|
+
summary.byClaimedBy.set(task.claimedBy, (summary.byClaimedBy.get(task.claimedBy) ?? 0) + 1);
|
|
1388
|
+
}
|
|
1389
|
+
summary.byKind.set(task.kind, (summary.byKind.get(task.kind) ?? 0) + 1);
|
|
1390
|
+
if (task.outcome) {
|
|
1391
|
+
summary.byOutcome.set(task.outcome, (summary.byOutcome.get(task.outcome) ?? 0) + 1);
|
|
1392
|
+
}
|
|
1393
|
+
if (task.target) {
|
|
1394
|
+
summary.topTargets.set(task.target, (summary.topTargets.get(task.target) ?? 0) + 1);
|
|
1395
|
+
}
|
|
1396
|
+
if (task.assignee) {
|
|
1397
|
+
summary.topAssignees.set(task.assignee, (summary.topAssignees.get(task.assignee) ?? 0) + 1);
|
|
1398
|
+
}
|
|
1399
|
+
if (task.priority) {
|
|
1400
|
+
summary.byPriority.set(task.priority, (summary.byPriority.get(task.priority) ?? 0) + 1);
|
|
1401
|
+
}
|
|
1402
|
+
if (task.queue) {
|
|
1403
|
+
summary.byQueue.set(task.queue, (summary.byQueue.get(task.queue) ?? 0) + 1);
|
|
1404
|
+
summary.topQueues.set(task.queue, (summary.topQueues.get(task.queue) ?? 0) + 1);
|
|
1405
|
+
}
|
|
1406
|
+
if (isVoiceOpsTaskOverdue(task)) {
|
|
1407
|
+
summary.overdue += 1;
|
|
1408
|
+
}
|
|
1409
|
+
}
|
|
1410
|
+
return {
|
|
1411
|
+
byClaimedBy: [...summary.byClaimedBy.entries()].sort((left, right) => right[1] - left[1]),
|
|
1412
|
+
byKind: [...summary.byKind.entries()].sort((left, right) => right[1] - left[1]),
|
|
1413
|
+
byOutcome: [...summary.byOutcome.entries()].sort((left, right) => right[1] - left[1]),
|
|
1414
|
+
byPriority: [...summary.byPriority.entries()].sort((left, right) => right[1] - left[1]),
|
|
1415
|
+
byQueue: [...summary.byQueue.entries()].sort((left, right) => right[1] - left[1]),
|
|
1416
|
+
claimed: summary.claimed,
|
|
1417
|
+
done: summary.done,
|
|
1418
|
+
inProgress: summary.inProgress,
|
|
1419
|
+
open: summary.open,
|
|
1420
|
+
overdue: summary.overdue,
|
|
1421
|
+
topAssignees: [...summary.topAssignees.entries()].sort((left, right) => right[1] - left[1]),
|
|
1422
|
+
topQueues: [...summary.topQueues.entries()].sort((left, right) => right[1] - left[1]),
|
|
1423
|
+
topTargets: [...summary.topTargets.entries()].sort((left, right) => right[1] - left[1]),
|
|
1424
|
+
total: summary.total
|
|
1425
|
+
};
|
|
1426
|
+
};
|
|
1427
|
+
|
|
1428
|
+
// src/core/store.ts
|
|
1429
|
+
var createId = () => crypto.randomUUID();
|
|
1430
|
+
var createVoiceSessionRecord = (id, scenarioId) => ({
|
|
1431
|
+
committedTurnIds: [],
|
|
1432
|
+
createdAt: Date.now(),
|
|
1433
|
+
currentTurn: {
|
|
1434
|
+
finalText: "",
|
|
1435
|
+
lastSpeechAt: undefined,
|
|
1436
|
+
lastTranscriptAt: undefined,
|
|
1437
|
+
partialEndedAt: undefined,
|
|
1438
|
+
partialStartedAt: undefined,
|
|
1439
|
+
partialText: "",
|
|
1440
|
+
silenceStartedAt: undefined,
|
|
1441
|
+
transcripts: []
|
|
1442
|
+
},
|
|
1443
|
+
id,
|
|
1444
|
+
scenarioId,
|
|
1445
|
+
reconnect: { attempts: 0 },
|
|
1446
|
+
status: "active",
|
|
1447
|
+
transcripts: [],
|
|
1448
|
+
turns: [],
|
|
1449
|
+
lastCommittedTurn: {
|
|
1450
|
+
committedAt: 0,
|
|
1451
|
+
signature: "",
|
|
1452
|
+
text: "",
|
|
1453
|
+
transcriptIds: []
|
|
1454
|
+
}
|
|
1455
|
+
});
|
|
1456
|
+
var resetVoiceSessionRecord = (id, existing, scenarioId) => ({
|
|
1457
|
+
...createVoiceSessionRecord(id, scenarioId),
|
|
1458
|
+
metadata: existing?.metadata
|
|
1459
|
+
});
|
|
1460
|
+
var toVoiceSessionSummary = (session) => ({
|
|
1461
|
+
createdAt: session.createdAt,
|
|
1462
|
+
id: session.id,
|
|
1463
|
+
lastActivityAt: session.lastActivityAt,
|
|
1464
|
+
status: session.status,
|
|
1465
|
+
turnCount: session.turns.length
|
|
1466
|
+
});
|
|
1467
|
+
|
|
1468
|
+
// src/internal/html.ts
|
|
1469
|
+
var escapeHtml = (value) => String(value).replaceAll("&", "&").replaceAll("<", "<").replaceAll(">", ">").replaceAll('"', """).replaceAll("'", "'");
|
|
1470
|
+
|
|
1471
|
+
// src/core/trace.ts
|
|
1472
|
+
var createVoiceTraceEvent = (event) => ({
|
|
1473
|
+
...event,
|
|
1474
|
+
at: event.at,
|
|
1475
|
+
id: event.id ?? createVoiceTraceEventId({
|
|
1476
|
+
at: event.at,
|
|
1477
|
+
sessionId: event.sessionId,
|
|
1478
|
+
turnId: event.turnId,
|
|
1479
|
+
type: event.type
|
|
1480
|
+
})
|
|
1481
|
+
});
|
|
1482
|
+
var createVoiceTraceEventId = (event) => [
|
|
1483
|
+
event.sessionId,
|
|
1484
|
+
event.turnId ?? "session",
|
|
1485
|
+
event.type,
|
|
1486
|
+
String(event.at ?? Date.now()),
|
|
1487
|
+
crypto.randomUUID()
|
|
1488
|
+
].map(encodeURIComponent).join(":");
|
|
1489
|
+
var createVoiceTraceSinkDeliveryId = (events) => {
|
|
1490
|
+
const firstEvent = events[0];
|
|
1491
|
+
return [
|
|
1492
|
+
firstEvent?.sessionId ?? "trace",
|
|
1493
|
+
firstEvent?.traceId ?? "sink",
|
|
1494
|
+
String(firstEvent?.at ?? Date.now()),
|
|
1495
|
+
crypto.randomUUID()
|
|
1496
|
+
].map(encodeURIComponent).join(":");
|
|
1497
|
+
};
|
|
1498
|
+
var createVoiceTraceSinkDeliveryRecord = (input) => {
|
|
1499
|
+
const createdAt = input.createdAt ?? Date.now();
|
|
1500
|
+
return {
|
|
1501
|
+
createdAt,
|
|
1502
|
+
deliveredAt: input.deliveredAt,
|
|
1503
|
+
deliveryAttempts: input.deliveryAttempts,
|
|
1504
|
+
deliveryError: input.deliveryError,
|
|
1505
|
+
deliveryStatus: input.deliveryStatus ?? "pending",
|
|
1506
|
+
events: input.events,
|
|
1507
|
+
id: input.id ?? createVoiceTraceSinkDeliveryId(input.events),
|
|
1508
|
+
sinkDeliveries: input.sinkDeliveries,
|
|
1509
|
+
updatedAt: input.updatedAt ?? createdAt
|
|
1510
|
+
};
|
|
1511
|
+
};
|
|
1512
|
+
var matchesTraceFilter = (event, filter) => {
|
|
1513
|
+
if (filter.sessionId !== undefined && event.sessionId !== filter.sessionId) {
|
|
1514
|
+
return false;
|
|
1515
|
+
}
|
|
1516
|
+
if (filter.turnId !== undefined && event.turnId !== filter.turnId) {
|
|
1517
|
+
return false;
|
|
1518
|
+
}
|
|
1519
|
+
if (filter.scenarioId !== undefined && event.scenarioId !== filter.scenarioId) {
|
|
1520
|
+
return false;
|
|
1521
|
+
}
|
|
1522
|
+
if (filter.traceId !== undefined && event.traceId !== filter.traceId) {
|
|
1523
|
+
return false;
|
|
1524
|
+
}
|
|
1525
|
+
if (filter.type !== undefined) {
|
|
1526
|
+
const types = Array.isArray(filter.type) ? filter.type : [filter.type];
|
|
1527
|
+
if (!types.includes(event.type)) {
|
|
1528
|
+
return false;
|
|
1529
|
+
}
|
|
1530
|
+
}
|
|
1531
|
+
return true;
|
|
1532
|
+
};
|
|
1533
|
+
var createVoiceProofTraceStore = (options = {}) => {
|
|
1534
|
+
const proofStore = options.proofStore ?? createVoiceMemoryTraceEventStore();
|
|
1535
|
+
const scopedProofStore = options.scope ? createVoiceScopedTraceEventStore(proofStore, options.scope) : proofStore;
|
|
1536
|
+
return {
|
|
1537
|
+
append: async (event) => {
|
|
1538
|
+
const stored = await proofStore.append(event);
|
|
1539
|
+
await options.mirrorStore?.append(stored);
|
|
1540
|
+
return stored;
|
|
1541
|
+
},
|
|
1542
|
+
get: async (id) => await proofStore.get(id) ?? await options.mirrorStore?.get(id),
|
|
1543
|
+
list: (filter) => scopedProofStore.list(filter),
|
|
1544
|
+
remove: async (id) => {
|
|
1545
|
+
await Promise.all([
|
|
1546
|
+
proofStore.remove(id),
|
|
1547
|
+
options.mirrorStore?.remove(id) ?? Promise.resolve()
|
|
1548
|
+
]);
|
|
1549
|
+
}
|
|
1550
|
+
};
|
|
1551
|
+
};
|
|
1552
|
+
var createVoiceScopedTraceEventStore = (store, scope) => {
|
|
1553
|
+
const upstreamFilter = (filter = {}) => {
|
|
1554
|
+
const next = { ...filter };
|
|
1555
|
+
delete next.limit;
|
|
1556
|
+
if (scope.scenarioId !== undefined) {
|
|
1557
|
+
delete next.scenarioId;
|
|
1558
|
+
}
|
|
1559
|
+
if (scope.sessionId !== undefined) {
|
|
1560
|
+
delete next.sessionId;
|
|
1561
|
+
}
|
|
1562
|
+
if (scope.traceId !== undefined) {
|
|
1563
|
+
delete next.traceId;
|
|
1564
|
+
}
|
|
1565
|
+
if (scope.turnId !== undefined) {
|
|
1566
|
+
delete next.turnId;
|
|
1567
|
+
}
|
|
1568
|
+
if (scope.type !== undefined) {
|
|
1569
|
+
delete next.type;
|
|
1570
|
+
}
|
|
1571
|
+
return next;
|
|
1572
|
+
};
|
|
1573
|
+
const scopedFilter = (filter = {}) => ({
|
|
1574
|
+
...filter,
|
|
1575
|
+
...scope
|
|
1576
|
+
});
|
|
1577
|
+
return {
|
|
1578
|
+
append: (event) => store.append(event),
|
|
1579
|
+
get: (id) => store.get(id),
|
|
1580
|
+
list: async (filter) => filterVoiceTraceEvents(await store.list(upstreamFilter(filter)), scopedFilter(filter)),
|
|
1581
|
+
remove: (id) => store.remove(id)
|
|
1582
|
+
};
|
|
1583
|
+
};
|
|
1584
|
+
var filterVoiceTraceEvents = (events, filter = {}) => {
|
|
1585
|
+
const sorted = events.filter((event) => matchesTraceFilter(event, filter)).sort((left, right) => left.at - right.at || left.id.localeCompare(right.id));
|
|
1586
|
+
return typeof filter.limit === "number" && filter.limit >= 0 ? sorted.slice(0, filter.limit) : sorted;
|
|
1587
|
+
};
|
|
1588
|
+
var isPruneTimeMatch = (event, options) => {
|
|
1589
|
+
if (typeof options.before === "number" && event.at >= options.before) {
|
|
1590
|
+
return false;
|
|
1591
|
+
}
|
|
1592
|
+
if (typeof options.beforeOrAt === "number" && event.at > options.beforeOrAt) {
|
|
1593
|
+
return false;
|
|
1594
|
+
}
|
|
1595
|
+
return true;
|
|
1596
|
+
};
|
|
1597
|
+
var pruneVoiceTraceEvents = async (options) => {
|
|
1598
|
+
const events = await options.store.list(options.filter);
|
|
1599
|
+
const deleted = selectVoiceTraceEventsForPrune(events, options);
|
|
1600
|
+
if (!options.dryRun) {
|
|
1601
|
+
await Promise.all(deleted.map((event) => options.store.remove(event.id)));
|
|
1602
|
+
}
|
|
1603
|
+
return {
|
|
1604
|
+
deleted,
|
|
1605
|
+
deletedCount: deleted.length,
|
|
1606
|
+
dryRun: Boolean(options.dryRun),
|
|
1607
|
+
scannedCount: events.length
|
|
1608
|
+
};
|
|
1609
|
+
};
|
|
1610
|
+
var selectVoiceTraceEventsForPrune = (events, options = {}) => {
|
|
1611
|
+
let candidates = filterVoiceTraceEvents(events, options.filter).filter((event) => isPruneTimeMatch(event, options));
|
|
1612
|
+
if (typeof options.keepNewest === "number" && options.keepNewest >= 0) {
|
|
1613
|
+
const newestIds = new Set([...candidates].sort((left, right) => right.at - left.at || right.id.localeCompare(left.id)).slice(0, options.keepNewest).map((event) => event.id));
|
|
1614
|
+
candidates = candidates.filter((event) => !newestIds.has(event.id));
|
|
1615
|
+
}
|
|
1616
|
+
return typeof options.limit === "number" && options.limit >= 0 ? candidates.slice(0, options.limit) : candidates;
|
|
1617
|
+
};
|
|
1618
|
+
var sleep2 = async (delayMs) => {
|
|
1619
|
+
if (delayMs <= 0) {
|
|
1620
|
+
return;
|
|
1621
|
+
}
|
|
1622
|
+
await new Promise((resolve) => setTimeout(resolve, delayMs));
|
|
1623
|
+
};
|
|
1624
|
+
var toHex2 = (bytes) => Array.from(bytes, (byte) => byte.toString(16).padStart(2, "0")).join("");
|
|
1625
|
+
var signVoiceTraceSinkBody = async (input) => {
|
|
1626
|
+
const encoder = new TextEncoder;
|
|
1627
|
+
const key = await crypto.subtle.importKey("raw", encoder.encode(input.secret), {
|
|
1628
|
+
hash: "SHA-256",
|
|
1629
|
+
name: "HMAC"
|
|
1630
|
+
}, false, ["sign"]);
|
|
1631
|
+
const payload = encoder.encode(`${input.timestamp}.${input.body}`);
|
|
1632
|
+
const signature = await crypto.subtle.sign("HMAC", key, payload);
|
|
1633
|
+
return `sha256=${toHex2(new Uint8Array(signature))}`;
|
|
1634
|
+
};
|
|
1635
|
+
var createVoiceTraceSinkDeliveryError = (input) => {
|
|
1636
|
+
if (input.response) {
|
|
1637
|
+
const statusText = input.response.statusText?.trim();
|
|
1638
|
+
return `Attempt ${input.attempt} failed with trace sink response ${input.response.status}${statusText ? ` ${statusText}` : ""}.`;
|
|
1639
|
+
}
|
|
1640
|
+
if (input.error instanceof Error) {
|
|
1641
|
+
return `Attempt ${input.attempt} failed: ${input.error.message}`;
|
|
1642
|
+
}
|
|
1643
|
+
return `Attempt ${input.attempt} failed: ${String(input.error)}`;
|
|
1644
|
+
};
|
|
1645
|
+
var normalizeVoiceTraceS3KeyPrefix = (prefix) => prefix?.trim().replace(/^\/+|\/+$/g, "") ?? "voice/trace-deliveries";
|
|
1646
|
+
var createVoiceTraceS3ObjectKey = (prefix, events) => {
|
|
1647
|
+
const firstEvent = events[0];
|
|
1648
|
+
const safeSessionId = encodeURIComponent(firstEvent?.sessionId ?? "trace");
|
|
1649
|
+
const safeEventId = encodeURIComponent(firstEvent?.id ?? crypto.randomUUID());
|
|
1650
|
+
return `${prefix}/${safeSessionId}/${Date.now()}-${safeEventId}.json`;
|
|
1651
|
+
};
|
|
1652
|
+
var resolveVoiceS3DeliveredTo = (options, key) => {
|
|
1653
|
+
const { bucket } = options;
|
|
1654
|
+
return bucket ? `s3://${bucket}/${key}` : `s3://${key}`;
|
|
1655
|
+
};
|
|
1656
|
+
var aggregateVoiceTraceSinkDeliveryStatus = (deliveries) => {
|
|
1657
|
+
const statuses = Object.values(deliveries).map((delivery) => delivery.status);
|
|
1658
|
+
if (statuses.length === 0 || statuses.every((status) => status === "skipped")) {
|
|
1659
|
+
return "skipped";
|
|
1660
|
+
}
|
|
1661
|
+
if (statuses.some((status) => status === "failed")) {
|
|
1662
|
+
return "failed";
|
|
1663
|
+
}
|
|
1664
|
+
return "delivered";
|
|
1665
|
+
};
|
|
1666
|
+
var createVoiceTraceHTTPSink = (options) => ({
|
|
1667
|
+
eventTypes: options.eventTypes,
|
|
1668
|
+
id: options.id,
|
|
1669
|
+
kind: options.kind ?? "http",
|
|
1670
|
+
deliver: async ({ events }) => {
|
|
1671
|
+
const fetchImpl = options.fetch ?? globalThis.fetch;
|
|
1672
|
+
if (typeof fetchImpl !== "function") {
|
|
1673
|
+
return {
|
|
1674
|
+
attempts: 0,
|
|
1675
|
+
deliveredTo: options.url,
|
|
1676
|
+
error: "Trace sink delivery failed: fetch is not available in this runtime.",
|
|
1677
|
+
eventCount: events.length,
|
|
1678
|
+
status: "failed"
|
|
1679
|
+
};
|
|
1680
|
+
}
|
|
1681
|
+
const maxRetries = Math.max(0, options.retries ?? 0);
|
|
1682
|
+
const backoffMs = Math.max(0, options.backoffMs ?? 250);
|
|
1683
|
+
const timeoutMs = Math.max(0, options.timeoutMs ?? 1e4);
|
|
1684
|
+
const payload = options.body ? await options.body({ events }) : {
|
|
1685
|
+
eventCount: events.length,
|
|
1686
|
+
events,
|
|
1687
|
+
source: "absolutejs-voice"
|
|
1688
|
+
};
|
|
1689
|
+
const body = JSON.stringify(payload);
|
|
1690
|
+
let lastError = "Trace sink delivery failed.";
|
|
1691
|
+
for (let attempt = 1;attempt <= maxRetries + 1; attempt += 1) {
|
|
1692
|
+
let controller;
|
|
1693
|
+
let timeout;
|
|
1694
|
+
try {
|
|
1695
|
+
const headers = {
|
|
1696
|
+
"content-type": "application/json",
|
|
1697
|
+
...options.headers
|
|
1698
|
+
};
|
|
1699
|
+
if (options.signingSecret) {
|
|
1700
|
+
const timestamp = String(Date.now());
|
|
1701
|
+
headers["x-absolutejs-timestamp"] = timestamp;
|
|
1702
|
+
headers["x-absolutejs-signature"] = await signVoiceTraceSinkBody({
|
|
1703
|
+
body,
|
|
1704
|
+
secret: options.signingSecret,
|
|
1705
|
+
timestamp
|
|
1706
|
+
});
|
|
1707
|
+
}
|
|
1708
|
+
controller = timeoutMs > 0 ? new AbortController : undefined;
|
|
1709
|
+
if (controller && timeoutMs > 0) {
|
|
1710
|
+
timeout = setTimeout(() => controller?.abort(), timeoutMs);
|
|
1711
|
+
}
|
|
1712
|
+
const response = await fetchImpl(options.url, {
|
|
1713
|
+
body,
|
|
1714
|
+
headers,
|
|
1715
|
+
method: options.method ?? "POST",
|
|
1716
|
+
signal: controller?.signal
|
|
1717
|
+
});
|
|
1718
|
+
if (response.ok) {
|
|
1719
|
+
let responseBody;
|
|
1720
|
+
try {
|
|
1721
|
+
responseBody = await response.clone().json();
|
|
1722
|
+
} catch {
|
|
1723
|
+
responseBody = undefined;
|
|
1724
|
+
}
|
|
1725
|
+
return {
|
|
1726
|
+
attempts: attempt,
|
|
1727
|
+
deliveredAt: Date.now(),
|
|
1728
|
+
deliveredTo: options.url,
|
|
1729
|
+
eventCount: events.length,
|
|
1730
|
+
responseBody,
|
|
1731
|
+
status: "delivered"
|
|
1732
|
+
};
|
|
1733
|
+
}
|
|
1734
|
+
lastError = createVoiceTraceSinkDeliveryError({
|
|
1735
|
+
attempt,
|
|
1736
|
+
response
|
|
1737
|
+
});
|
|
1738
|
+
} catch (error) {
|
|
1739
|
+
lastError = createVoiceTraceSinkDeliveryError({
|
|
1740
|
+
attempt,
|
|
1741
|
+
error
|
|
1742
|
+
});
|
|
1743
|
+
} finally {
|
|
1744
|
+
if (timeout) {
|
|
1745
|
+
clearTimeout(timeout);
|
|
1746
|
+
}
|
|
1747
|
+
}
|
|
1748
|
+
if (attempt <= maxRetries) {
|
|
1749
|
+
await sleep2(backoffMs * attempt);
|
|
1750
|
+
}
|
|
1751
|
+
}
|
|
1752
|
+
return {
|
|
1753
|
+
attempts: maxRetries + 1,
|
|
1754
|
+
deliveredTo: options.url,
|
|
1755
|
+
error: lastError,
|
|
1756
|
+
eventCount: events.length,
|
|
1757
|
+
status: "failed"
|
|
1758
|
+
};
|
|
1759
|
+
}
|
|
1760
|
+
});
|
|
1761
|
+
var createVoiceTraceS3Sink = (options) => {
|
|
1762
|
+
const client = options.client ?? new Bun.S3Client(options);
|
|
1763
|
+
const keyPrefix = normalizeVoiceTraceS3KeyPrefix(options.keyPrefix);
|
|
1764
|
+
return {
|
|
1765
|
+
eventTypes: options.eventTypes,
|
|
1766
|
+
id: options.id,
|
|
1767
|
+
kind: options.kind ?? "s3",
|
|
1768
|
+
deliver: async ({ events }) => {
|
|
1769
|
+
const key = createVoiceTraceS3ObjectKey(keyPrefix, events);
|
|
1770
|
+
const payload = options.body ? await options.body({ events, key }) : {
|
|
1771
|
+
eventCount: events.length,
|
|
1772
|
+
events,
|
|
1773
|
+
key,
|
|
1774
|
+
source: "absolutejs-voice"
|
|
1775
|
+
};
|
|
1776
|
+
try {
|
|
1777
|
+
const file = client.file(key, options);
|
|
1778
|
+
await file.write(JSON.stringify(payload), {
|
|
1779
|
+
type: options.contentType ?? "application/json"
|
|
1780
|
+
});
|
|
1781
|
+
return {
|
|
1782
|
+
attempts: 1,
|
|
1783
|
+
deliveredAt: Date.now(),
|
|
1784
|
+
deliveredTo: resolveVoiceS3DeliveredTo(options, key),
|
|
1785
|
+
eventCount: events.length,
|
|
1786
|
+
responseBody: { key },
|
|
1787
|
+
status: "delivered"
|
|
1788
|
+
};
|
|
1789
|
+
} catch (error) {
|
|
1790
|
+
return {
|
|
1791
|
+
attempts: 1,
|
|
1792
|
+
deliveredTo: resolveVoiceS3DeliveredTo(options, key),
|
|
1793
|
+
error: error instanceof Error ? error.message : String(error),
|
|
1794
|
+
eventCount: events.length,
|
|
1795
|
+
status: "failed"
|
|
1796
|
+
};
|
|
1797
|
+
}
|
|
1798
|
+
}
|
|
1799
|
+
};
|
|
1800
|
+
};
|
|
1801
|
+
var createVoiceTraceSinkStore = (options) => {
|
|
1802
|
+
const deliver = async (event) => {
|
|
1803
|
+
const result = await deliverVoiceTraceEventsToSinks({
|
|
1804
|
+
events: [event],
|
|
1805
|
+
redact: options.redact,
|
|
1806
|
+
sinks: options.sinks
|
|
1807
|
+
});
|
|
1808
|
+
await options.onDelivery?.(result);
|
|
1809
|
+
};
|
|
1810
|
+
return {
|
|
1811
|
+
append: async (event) => {
|
|
1812
|
+
const stored = await options.store.append(event);
|
|
1813
|
+
if (options.deliveryQueue) {
|
|
1814
|
+
const delivery2 = createVoiceTraceSinkDeliveryRecord({
|
|
1815
|
+
events: [stored]
|
|
1816
|
+
});
|
|
1817
|
+
await options.deliveryQueue.set(delivery2.id, delivery2);
|
|
1818
|
+
return stored;
|
|
1819
|
+
}
|
|
1820
|
+
const delivery = deliver(stored);
|
|
1821
|
+
if (options.awaitDelivery) {
|
|
1822
|
+
await delivery;
|
|
1823
|
+
} else {
|
|
1824
|
+
delivery.catch((error) => {
|
|
1825
|
+
options.onError?.(error);
|
|
1826
|
+
});
|
|
1827
|
+
}
|
|
1828
|
+
return stored;
|
|
1829
|
+
},
|
|
1830
|
+
get: (id) => options.store.get(id),
|
|
1831
|
+
list: (filter) => options.store.list(filter),
|
|
1832
|
+
remove: (id) => options.store.remove(id)
|
|
1833
|
+
};
|
|
1834
|
+
};
|
|
1835
|
+
var deliverVoiceTraceEventsToSinks = async (input) => {
|
|
1836
|
+
const events = input.redact ? redactVoiceTraceEvents(input.events, input.redact) : input.events;
|
|
1837
|
+
const sinkDeliveries = {};
|
|
1838
|
+
for (const sink of input.sinks) {
|
|
1839
|
+
const sinkEvents = sink.eventTypes?.length ? events.filter((event) => sink.eventTypes?.includes(event.type)) : events;
|
|
1840
|
+
if (sinkEvents.length === 0) {
|
|
1841
|
+
sinkDeliveries[sink.id] = {
|
|
1842
|
+
attempts: 0,
|
|
1843
|
+
eventCount: 0,
|
|
1844
|
+
status: "skipped"
|
|
1845
|
+
};
|
|
1846
|
+
continue;
|
|
1847
|
+
}
|
|
1848
|
+
try {
|
|
1849
|
+
sinkDeliveries[sink.id] = await sink.deliver({
|
|
1850
|
+
events: sinkEvents
|
|
1851
|
+
});
|
|
1852
|
+
} catch (error) {
|
|
1853
|
+
sinkDeliveries[sink.id] = {
|
|
1854
|
+
attempts: 1,
|
|
1855
|
+
error: error instanceof Error ? error.message : String(error),
|
|
1856
|
+
eventCount: sinkEvents.length,
|
|
1857
|
+
status: "failed"
|
|
1858
|
+
};
|
|
1859
|
+
}
|
|
1860
|
+
}
|
|
1861
|
+
return {
|
|
1862
|
+
deliveredAt: Date.now(),
|
|
1863
|
+
eventCount: events.length,
|
|
1864
|
+
sinkDeliveries,
|
|
1865
|
+
status: aggregateVoiceTraceSinkDeliveryStatus(sinkDeliveries)
|
|
1866
|
+
};
|
|
1867
|
+
};
|
|
1868
|
+
var normalizeVoiceProfileTraceTaggerProfile = (profile) => typeof profile === "string" ? { id: profile } : profile?.id ? profile : undefined;
|
|
1869
|
+
var createVoiceMemoryTraceEventStore = () => {
|
|
1870
|
+
const events = new Map;
|
|
1871
|
+
const append = async (event) => {
|
|
1872
|
+
const stored = createVoiceTraceEvent(event);
|
|
1873
|
+
events.set(stored.id, stored);
|
|
1874
|
+
return stored;
|
|
1875
|
+
};
|
|
1876
|
+
const get = async (id) => events.get(id);
|
|
1877
|
+
const list = async (filter) => filterVoiceTraceEvents([...events.values()], filter);
|
|
1878
|
+
const remove = async (id) => {
|
|
1879
|
+
events.delete(id);
|
|
1880
|
+
};
|
|
1881
|
+
return { append, get, list, remove };
|
|
1882
|
+
};
|
|
1883
|
+
var createVoiceMemoryTraceSinkDeliveryStore = () => {
|
|
1884
|
+
const deliveries = new Map;
|
|
1885
|
+
return {
|
|
1886
|
+
get: async (id) => deliveries.get(id),
|
|
1887
|
+
list: async () => [...deliveries.values()].sort((left, right) => left.createdAt - right.createdAt || left.id.localeCompare(right.id)),
|
|
1888
|
+
remove: async (id) => {
|
|
1889
|
+
deliveries.delete(id);
|
|
1890
|
+
},
|
|
1891
|
+
set: async (id, delivery) => {
|
|
1892
|
+
deliveries.set(id, delivery);
|
|
1893
|
+
}
|
|
1894
|
+
};
|
|
1895
|
+
};
|
|
1896
|
+
var createVoiceProfileTraceTagger = (options) => {
|
|
1897
|
+
const profiles = new Map((options.profiles ?? []).map((profile) => [profile.id, profile]));
|
|
1898
|
+
const defaultProfile = normalizeVoiceProfileTraceTaggerProfile(options.defaultProfile);
|
|
1899
|
+
const resolveProfile = async (event) => {
|
|
1900
|
+
const resolved = normalizeVoiceProfileTraceTaggerProfile(await options.resolveProfile?.(event));
|
|
1901
|
+
const profile = resolved ?? defaultProfile;
|
|
1902
|
+
return profile ? profiles.get(profile.id) ?? profile : undefined;
|
|
1903
|
+
};
|
|
1904
|
+
return {
|
|
1905
|
+
append: async (event) => {
|
|
1906
|
+
const profile = await resolveProfile(event);
|
|
1907
|
+
if (!profile) {
|
|
1908
|
+
return options.store.append(event);
|
|
1909
|
+
}
|
|
1910
|
+
const metadata = {
|
|
1911
|
+
...event.metadata ?? {},
|
|
1912
|
+
benchmarkProfileId: profile.id,
|
|
1913
|
+
profileDescription: event.metadata?.profileDescription ?? profile.description,
|
|
1914
|
+
profileId: profile.id,
|
|
1915
|
+
profileLabel: event.metadata?.profileLabel ?? profile.label
|
|
1916
|
+
};
|
|
1917
|
+
const payload = event.payload && typeof event.payload === "object" ? {
|
|
1918
|
+
...event.payload,
|
|
1919
|
+
benchmarkProfileId: event.payload.benchmarkProfileId ?? profile.id,
|
|
1920
|
+
profileDescription: event.payload.profileDescription ?? profile.description,
|
|
1921
|
+
profileId: event.payload.profileId ?? profile.id,
|
|
1922
|
+
profileLabel: event.payload.profileLabel ?? profile.label
|
|
1923
|
+
} : event.payload;
|
|
1924
|
+
return options.store.append({
|
|
1925
|
+
...event,
|
|
1926
|
+
metadata,
|
|
1927
|
+
payload
|
|
1928
|
+
});
|
|
1929
|
+
},
|
|
1930
|
+
get: (id) => options.store.get(id),
|
|
1931
|
+
list: (filter) => options.store.list(filter),
|
|
1932
|
+
remove: (id) => options.store.remove(id)
|
|
1933
|
+
};
|
|
1934
|
+
};
|
|
1935
|
+
var exportVoiceTrace = async (input) => {
|
|
1936
|
+
const events = await input.store.list(input.filter);
|
|
1937
|
+
return {
|
|
1938
|
+
exportedAt: Date.now(),
|
|
1939
|
+
events: input.redact ? redactVoiceTraceEvents(events, input.redact) : events,
|
|
1940
|
+
filter: input.filter,
|
|
1941
|
+
redacted: Boolean(input.redact)
|
|
1942
|
+
};
|
|
1943
|
+
};
|
|
1944
|
+
var toNumber = (value) => typeof value === "number" && Number.isFinite(value) ? value : 0;
|
|
1945
|
+
var formatTraceValue = (value) => {
|
|
1946
|
+
if (value === undefined || value === null) {
|
|
1947
|
+
return "";
|
|
1948
|
+
}
|
|
1949
|
+
if (typeof value === "string") {
|
|
1950
|
+
return value;
|
|
1951
|
+
}
|
|
1952
|
+
if (typeof value === "number" || typeof value === "boolean") {
|
|
1953
|
+
return String(value);
|
|
1954
|
+
}
|
|
1955
|
+
try {
|
|
1956
|
+
return JSON.stringify(value);
|
|
1957
|
+
} catch {
|
|
1958
|
+
return String(value);
|
|
1959
|
+
}
|
|
1960
|
+
};
|
|
1961
|
+
var DEFAULT_REDACTION_KEYS = [
|
|
1962
|
+
"apiKey",
|
|
1963
|
+
"authorization",
|
|
1964
|
+
"creditCard",
|
|
1965
|
+
"email",
|
|
1966
|
+
"externalId",
|
|
1967
|
+
"password",
|
|
1968
|
+
"phone",
|
|
1969
|
+
"secret",
|
|
1970
|
+
"ssn",
|
|
1971
|
+
"token"
|
|
1972
|
+
];
|
|
1973
|
+
var DEFAULT_REDACTION_TEXT_KEYS = [
|
|
1974
|
+
"assistantText",
|
|
1975
|
+
"content",
|
|
1976
|
+
"error",
|
|
1977
|
+
"reason",
|
|
1978
|
+
"summary",
|
|
1979
|
+
"text"
|
|
1980
|
+
];
|
|
1981
|
+
var EMAIL_PATTERN = /\b[A-Z0-9._%+-]+@[A-Z0-9.-]+\.[A-Z]{2,}\b/gi;
|
|
1982
|
+
var PHONE_PATTERN = /(?<!\d)(?:\+?1[\s.-]?)?(?:\(?\d{3}\)?[\s.-]?)\d{3}[\s.-]?\d{4}(?!\d)/g;
|
|
1983
|
+
var normalizeRedactionKey = (key) => key.trim().toLowerCase().replace(/[^a-z0-9]/g, "");
|
|
1984
|
+
var resolveVoiceTraceRedactionOptions = (options = {}) => ({
|
|
1985
|
+
keys: typeof options === "boolean" ? DEFAULT_REDACTION_KEYS : options.keys ?? DEFAULT_REDACTION_KEYS,
|
|
1986
|
+
redactEmails: typeof options === "boolean" ? true : options.redactEmails ?? true,
|
|
1987
|
+
redactPhoneNumbers: typeof options === "boolean" ? true : options.redactPhoneNumbers ?? true,
|
|
1988
|
+
redactText: typeof options === "boolean" ? true : options.redactText ?? true,
|
|
1989
|
+
replacement: typeof options === "boolean" ? "[redacted]" : options.replacement ?? "[redacted]",
|
|
1990
|
+
textKeys: typeof options === "boolean" ? DEFAULT_REDACTION_TEXT_KEYS : options.textKeys ?? DEFAULT_REDACTION_TEXT_KEYS
|
|
1991
|
+
});
|
|
1992
|
+
var resolveReplacement = (input) => typeof input.options.replacement === "function" ? input.options.replacement({
|
|
1993
|
+
key: input.key,
|
|
1994
|
+
path: input.path,
|
|
1995
|
+
value: input.value
|
|
1996
|
+
}) : input.options.replacement;
|
|
1997
|
+
var redactVoiceTraceText = (value, options = {}, input = {}) => {
|
|
1998
|
+
const resolved = resolveVoiceTraceRedactionOptions(options);
|
|
1999
|
+
let redacted = value;
|
|
2000
|
+
const replacement = resolveReplacement({
|
|
2001
|
+
key: input.key,
|
|
2002
|
+
options: resolved,
|
|
2003
|
+
path: input.path ?? [],
|
|
2004
|
+
value
|
|
2005
|
+
});
|
|
2006
|
+
if (resolved.redactEmails) {
|
|
2007
|
+
redacted = redacted.replace(EMAIL_PATTERN, replacement);
|
|
2008
|
+
}
|
|
2009
|
+
if (resolved.redactPhoneNumbers) {
|
|
2010
|
+
redacted = redacted.replace(PHONE_PATTERN, replacement);
|
|
2011
|
+
}
|
|
2012
|
+
return redacted;
|
|
2013
|
+
};
|
|
2014
|
+
var redactTraceValue = (value, options, path) => {
|
|
2015
|
+
const key = path.at(-1);
|
|
2016
|
+
const normalizedKey = key ? normalizeRedactionKey(key) : undefined;
|
|
2017
|
+
const sensitiveKeys = new Set(options.keys.map(normalizeRedactionKey));
|
|
2018
|
+
const textKeys = new Set(options.textKeys.map(normalizeRedactionKey));
|
|
2019
|
+
if (normalizedKey && sensitiveKeys.has(normalizedKey) && (value === null || ["boolean", "number", "string", "undefined"].includes(typeof value))) {
|
|
2020
|
+
return resolveReplacement({
|
|
2021
|
+
key,
|
|
2022
|
+
options,
|
|
2023
|
+
path,
|
|
2024
|
+
value: String(value ?? "")
|
|
2025
|
+
});
|
|
2026
|
+
}
|
|
2027
|
+
if (typeof value === "string") {
|
|
2028
|
+
const shouldRedactText = options.redactText && (!normalizedKey || textKeys.has(normalizedKey) || path.length === 0);
|
|
2029
|
+
return shouldRedactText ? redactVoiceTraceText(value, options, {
|
|
2030
|
+
key,
|
|
2031
|
+
path
|
|
2032
|
+
}) : value;
|
|
2033
|
+
}
|
|
2034
|
+
if (Array.isArray(value)) {
|
|
2035
|
+
return value.map((item, index) => redactTraceValue(item, options, [...path, String(index)]));
|
|
2036
|
+
}
|
|
2037
|
+
if (typeof value === "object" && value) {
|
|
2038
|
+
return Object.fromEntries(Object.entries(value).map(([entryKey, entryValue]) => [
|
|
2039
|
+
entryKey,
|
|
2040
|
+
redactTraceValue(entryValue, options, [...path, entryKey])
|
|
2041
|
+
]));
|
|
2042
|
+
}
|
|
2043
|
+
return value;
|
|
2044
|
+
};
|
|
2045
|
+
var evaluateVoiceTrace = (events, options = {}) => {
|
|
2046
|
+
const summary = summarizeVoiceTrace(events);
|
|
2047
|
+
const issues = [];
|
|
2048
|
+
const maxHandoffs = options.maxHandoffs ?? 3;
|
|
2049
|
+
const maxToolErrors = options.maxToolErrors ?? 0;
|
|
2050
|
+
const maxModelCallsPerTurn = options.maxModelCallsPerTurn ?? 6;
|
|
2051
|
+
const turnCountForRatio = Math.max(1, summary.turnCount);
|
|
2052
|
+
if (options.requireCompletedCall !== false && !summary.endedAt) {
|
|
2053
|
+
issues.push({
|
|
2054
|
+
code: "call-not-ended",
|
|
2055
|
+
message: "Trace does not include a call end lifecycle event.",
|
|
2056
|
+
severity: "warning"
|
|
2057
|
+
});
|
|
2058
|
+
}
|
|
2059
|
+
if (summary.failed) {
|
|
2060
|
+
issues.push({
|
|
2061
|
+
code: "session-error",
|
|
2062
|
+
message: "Trace contains a session error or failed call disposition.",
|
|
2063
|
+
severity: "error"
|
|
2064
|
+
});
|
|
2065
|
+
}
|
|
2066
|
+
if (options.requireTranscript !== false && summary.transcriptCount === 0) {
|
|
2067
|
+
issues.push({
|
|
2068
|
+
code: "missing-transcript",
|
|
2069
|
+
message: "Trace does not include any transcript events.",
|
|
2070
|
+
severity: "error"
|
|
2071
|
+
});
|
|
2072
|
+
}
|
|
2073
|
+
if (options.requireTurn !== false && summary.turnCount === 0) {
|
|
2074
|
+
issues.push({
|
|
2075
|
+
code: "missing-turn",
|
|
2076
|
+
message: "Trace does not include any committed turns.",
|
|
2077
|
+
severity: "error"
|
|
2078
|
+
});
|
|
2079
|
+
}
|
|
2080
|
+
if (options.requireAssistantReply !== false && summary.turnCount > 0 && summary.assistantReplyCount === 0) {
|
|
2081
|
+
issues.push({
|
|
2082
|
+
code: "missing-assistant-reply",
|
|
2083
|
+
message: "Trace has committed turns but no assistant replies.",
|
|
2084
|
+
severity: "warning"
|
|
2085
|
+
});
|
|
2086
|
+
}
|
|
2087
|
+
if (summary.toolErrorCount > maxToolErrors) {
|
|
2088
|
+
issues.push({
|
|
2089
|
+
code: "tool-errors",
|
|
2090
|
+
message: `Trace has ${summary.toolErrorCount} tool error(s), above the allowed ${maxToolErrors}.`,
|
|
2091
|
+
severity: "error"
|
|
2092
|
+
});
|
|
2093
|
+
}
|
|
2094
|
+
if (summary.handoffCount > maxHandoffs) {
|
|
2095
|
+
issues.push({
|
|
2096
|
+
code: "too-many-handoffs",
|
|
2097
|
+
message: `Trace has ${summary.handoffCount} handoff(s), above the allowed ${maxHandoffs}.`,
|
|
2098
|
+
severity: "warning"
|
|
2099
|
+
});
|
|
2100
|
+
}
|
|
2101
|
+
if (summary.modelCallCount / turnCountForRatio > maxModelCallsPerTurn) {
|
|
2102
|
+
issues.push({
|
|
2103
|
+
code: "too-many-model-calls",
|
|
2104
|
+
message: `Trace averages more than ${maxModelCallsPerTurn} model calls per committed turn.`,
|
|
2105
|
+
severity: "warning"
|
|
2106
|
+
});
|
|
2107
|
+
}
|
|
2108
|
+
return {
|
|
2109
|
+
issues,
|
|
2110
|
+
pass: !issues.some((issue) => issue.severity === "error"),
|
|
2111
|
+
summary
|
|
2112
|
+
};
|
|
2113
|
+
};
|
|
2114
|
+
var redactVoiceTraceEvent = (event, options = {}) => {
|
|
2115
|
+
const resolved = resolveVoiceTraceRedactionOptions(options);
|
|
2116
|
+
return {
|
|
2117
|
+
...event,
|
|
2118
|
+
metadata: redactTraceValue(event.metadata, resolved, [
|
|
2119
|
+
"metadata"
|
|
2120
|
+
]),
|
|
2121
|
+
payload: redactTraceValue(event.payload, resolved, [
|
|
2122
|
+
"payload"
|
|
2123
|
+
])
|
|
2124
|
+
};
|
|
2125
|
+
};
|
|
2126
|
+
var redactVoiceTraceEvents = (events, options = {}) => events.map((event) => redactVoiceTraceEvent(event, options));
|
|
2127
|
+
var summarizeVoiceTrace = (events) => {
|
|
2128
|
+
const sorted = filterVoiceTraceEvents(events);
|
|
2129
|
+
const firstEvent = sorted[0];
|
|
2130
|
+
const lastEvent = sorted.at(-1);
|
|
2131
|
+
const lifecycleEvents = sorted.filter((event) => event.type === "call.lifecycle");
|
|
2132
|
+
const startEvent = lifecycleEvents.find((event) => event.payload.type === "start");
|
|
2133
|
+
const endEvent = lifecycleEvents.toReversed().find((event) => event.payload.type === "end");
|
|
2134
|
+
const costEvents = sorted.filter((event) => event.type === "turn.cost");
|
|
2135
|
+
const toolEvents = sorted.filter((event) => event.type === "agent.tool");
|
|
2136
|
+
const startedAt = startEvent?.at ?? firstEvent?.at;
|
|
2137
|
+
const endedAt = endEvent?.at ?? lastEvent?.at;
|
|
2138
|
+
const failed = sorted.some((event) => event.type === "session.error") || endEvent?.payload.disposition === "failed";
|
|
2139
|
+
return {
|
|
2140
|
+
assistantReplyCount: sorted.filter((event) => event.type === "turn.assistant").length,
|
|
2141
|
+
callDurationMs: startedAt !== undefined && endedAt !== undefined ? Math.max(0, endedAt - startedAt) : undefined,
|
|
2142
|
+
cost: {
|
|
2143
|
+
estimatedRelativeCostUnits: costEvents.reduce((total, event) => total + toNumber(event.payload.estimatedRelativeCostUnits), 0),
|
|
2144
|
+
totalBillableAudioMs: costEvents.reduce((total, event) => total + toNumber(event.payload.totalBillableAudioMs), 0)
|
|
2145
|
+
},
|
|
2146
|
+
endedAt,
|
|
2147
|
+
errorCount: sorted.filter((event) => event.type === "session.error").length,
|
|
2148
|
+
eventCount: sorted.length,
|
|
2149
|
+
failed,
|
|
2150
|
+
handoffCount: sorted.filter((event) => event.type === "agent.handoff").length,
|
|
2151
|
+
modelCallCount: sorted.filter((event) => event.type === "agent.model").length,
|
|
2152
|
+
sessionId: firstEvent?.sessionId,
|
|
2153
|
+
startedAt,
|
|
2154
|
+
toolCallCount: toolEvents.length,
|
|
2155
|
+
toolErrorCount: toolEvents.filter((event) => event.payload.status === "error").length,
|
|
2156
|
+
traceId: firstEvent?.traceId,
|
|
2157
|
+
transcriptCount: sorted.filter((event) => event.type === "turn.transcript").length,
|
|
2158
|
+
turnCount: sorted.filter((event) => event.type === "turn.committed").length
|
|
2159
|
+
};
|
|
2160
|
+
};
|
|
2161
|
+
var renderTraceEventMarkdown = (event, startedAt) => {
|
|
2162
|
+
const offset = startedAt === undefined ? `${event.at}` : `+${Math.max(0, event.at - startedAt)}ms`;
|
|
2163
|
+
const label = `- ${offset} [${event.type}]`;
|
|
2164
|
+
switch (event.type) {
|
|
2165
|
+
case "turn.transcript":
|
|
2166
|
+
return `${label} ${event.payload.isFinal ? "final" : "partial"} "${formatTraceValue(event.payload.text)}"`;
|
|
2167
|
+
case "turn.committed":
|
|
2168
|
+
return `${label} committed "${formatTraceValue(event.payload.text)}"`;
|
|
2169
|
+
case "turn.assistant":
|
|
2170
|
+
return event.payload.text ? `${label} assistant "${formatTraceValue(event.payload.text)}"` : `${label} ${formatTraceValue(event.payload.status)}`;
|
|
2171
|
+
case "agent.tool":
|
|
2172
|
+
return `${label} ${formatTraceValue(event.payload.toolName)} ${formatTraceValue(event.payload.status)}`;
|
|
2173
|
+
case "agent.context":
|
|
2174
|
+
return `${label} ${formatTraceValue(event.payload.fromAgentId)} -> ${formatTraceValue(event.payload.targetAgentId)} ${formatTraceValue(event.payload.status)}`;
|
|
2175
|
+
case "agent.handoff":
|
|
2176
|
+
return `${label} ${formatTraceValue(event.payload.fromAgentId)} -> ${formatTraceValue(event.payload.targetAgentId)}`;
|
|
2177
|
+
case "session.error":
|
|
2178
|
+
return `${label} ${formatTraceValue(event.payload.error)}`;
|
|
2179
|
+
case "call.lifecycle":
|
|
2180
|
+
return `${label} ${formatTraceValue(event.payload.type)} ${formatTraceValue(event.payload.disposition)}`.trim();
|
|
2181
|
+
default:
|
|
2182
|
+
return `${label} ${formatTraceValue(event.payload)}`;
|
|
2183
|
+
}
|
|
2184
|
+
};
|
|
2185
|
+
var buildVoiceTraceReplay = (events, options = {}) => ({
|
|
2186
|
+
evaluation: evaluateVoiceTrace(options.redact ? redactVoiceTraceEvents(events, options.redact) : events, options.evaluation),
|
|
2187
|
+
html: renderVoiceTraceHTML(events, options),
|
|
2188
|
+
markdown: renderVoiceTraceMarkdown(events, options),
|
|
2189
|
+
summary: summarizeVoiceTrace(options.redact ? redactVoiceTraceEvents(events, options.redact) : events)
|
|
2190
|
+
});
|
|
2191
|
+
var renderVoiceTraceHTML = (events, options = {}) => {
|
|
2192
|
+
const markdown = renderVoiceTraceMarkdown(events, options);
|
|
2193
|
+
const renderEvents = options.redact ? redactVoiceTraceEvents(events, options.redact) : events;
|
|
2194
|
+
const summary = summarizeVoiceTrace(renderEvents);
|
|
2195
|
+
const evaluation = evaluateVoiceTrace(renderEvents, options.evaluation);
|
|
2196
|
+
const eventRows = filterVoiceTraceEvents(renderEvents).map((event) => {
|
|
2197
|
+
const offset = summary.startedAt === undefined ? event.at : Math.max(0, event.at - summary.startedAt);
|
|
2198
|
+
return [
|
|
2199
|
+
"<tr>",
|
|
2200
|
+
`<td>${escapeHtml(String(offset))}</td>`,
|
|
2201
|
+
`<td>${escapeHtml(event.type)}</td>`,
|
|
2202
|
+
`<td>${escapeHtml(event.turnId ?? "")}</td>`,
|
|
2203
|
+
`<td><code>${escapeHtml(JSON.stringify(event.payload))}</code></td>`,
|
|
2204
|
+
"</tr>"
|
|
2205
|
+
].join("");
|
|
2206
|
+
}).join(`
|
|
2207
|
+
`);
|
|
2208
|
+
return [
|
|
2209
|
+
"<!doctype html>",
|
|
2210
|
+
'<html lang="en">',
|
|
2211
|
+
"<head>",
|
|
2212
|
+
'<meta charset="utf-8" />',
|
|
2213
|
+
'<meta name="viewport" content="width=device-width, initial-scale=1" />',
|
|
2214
|
+
`<title>${escapeHtml(options.title ?? "Voice Trace")}</title>`,
|
|
2215
|
+
"<style>",
|
|
2216
|
+
"body{font-family:ui-sans-serif,system-ui,sans-serif;margin:2rem;line-height:1.45;background:#f8f7f2;color:#181713}",
|
|
2217
|
+
"main{max-width:1100px;margin:auto}",
|
|
2218
|
+
".summary{display:grid;grid-template-columns:repeat(auto-fit,minmax(160px,1fr));gap:.75rem;margin:1rem 0}",
|
|
2219
|
+
".card{background:white;border:1px solid #ded9cc;border-radius:12px;padding:1rem}",
|
|
2220
|
+
".pass{color:#126b3a}.fail{color:#9d2222}",
|
|
2221
|
+
"table{border-collapse:collapse;width:100%;background:white;border:1px solid #ded9cc}",
|
|
2222
|
+
"th,td{border-bottom:1px solid #eee8dc;padding:.65rem;text-align:left;vertical-align:top}",
|
|
2223
|
+
"code{white-space:pre-wrap;word-break:break-word}",
|
|
2224
|
+
"pre{background:#181713;color:#f8f7f2;padding:1rem;border-radius:12px;overflow:auto}",
|
|
2225
|
+
"</style>",
|
|
2226
|
+
"</head>",
|
|
2227
|
+
"<body><main>",
|
|
2228
|
+
`<h1>${escapeHtml(options.title ?? `Voice Trace ${summary.sessionId ?? ""}`.trim())}</h1>`,
|
|
2229
|
+
`<p class="${evaluation.pass ? "pass" : "fail"}">QA: ${evaluation.pass ? "pass" : "fail"}</p>`,
|
|
2230
|
+
'<section class="summary">',
|
|
2231
|
+
`<div class="card"><strong>Events</strong><br>${summary.eventCount}</div>`,
|
|
2232
|
+
`<div class="card"><strong>Turns</strong><br>${summary.turnCount}</div>`,
|
|
2233
|
+
`<div class="card"><strong>Transcripts</strong><br>${summary.transcriptCount}</div>`,
|
|
2234
|
+
`<div class="card"><strong>Tool errors</strong><br>${summary.toolErrorCount}</div>`,
|
|
2235
|
+
`<div class="card"><strong>Cost units</strong><br>${summary.cost.estimatedRelativeCostUnits}</div>`,
|
|
2236
|
+
"</section>",
|
|
2237
|
+
"<h2>Timeline</h2>",
|
|
2238
|
+
"<table><thead><tr><th>Offset ms</th><th>Type</th><th>Turn</th><th>Payload</th></tr></thead><tbody>",
|
|
2239
|
+
eventRows,
|
|
2240
|
+
"</tbody></table>",
|
|
2241
|
+
"<h2>Markdown Export</h2>",
|
|
2242
|
+
`<pre>${escapeHtml(markdown)}</pre>`,
|
|
2243
|
+
"</main></body></html>"
|
|
2244
|
+
].join(`
|
|
2245
|
+
`);
|
|
2246
|
+
};
|
|
2247
|
+
var renderVoiceTraceMarkdown = (events, options = {}) => {
|
|
2248
|
+
const sorted = filterVoiceTraceEvents(options.redact ? redactVoiceTraceEvents(events, options.redact) : events);
|
|
2249
|
+
const summary = summarizeVoiceTrace(sorted);
|
|
2250
|
+
const evaluation = evaluateVoiceTrace(sorted, options.evaluation);
|
|
2251
|
+
const lines = [
|
|
2252
|
+
`# ${options.title ?? `Voice Trace ${summary.sessionId ?? ""}`.trim()}`,
|
|
2253
|
+
"",
|
|
2254
|
+
`Pass: ${evaluation.pass ? "yes" : "no"}`,
|
|
2255
|
+
`Session: ${summary.sessionId ?? "unknown"}`,
|
|
2256
|
+
`Events: ${summary.eventCount}`,
|
|
2257
|
+
`Turns: ${summary.turnCount}`,
|
|
2258
|
+
`Transcripts: ${summary.transcriptCount}`,
|
|
2259
|
+
`Assistant replies: ${summary.assistantReplyCount}`,
|
|
2260
|
+
`Model calls: ${summary.modelCallCount}`,
|
|
2261
|
+
`Tool calls: ${summary.toolCallCount}`,
|
|
2262
|
+
`Handoffs: ${summary.handoffCount}`,
|
|
2263
|
+
`Errors: ${summary.errorCount}`,
|
|
2264
|
+
`Estimated cost units: ${summary.cost.estimatedRelativeCostUnits}`,
|
|
2265
|
+
""
|
|
2266
|
+
];
|
|
2267
|
+
if (evaluation.issues.length > 0) {
|
|
2268
|
+
lines.push("## Issues", "");
|
|
2269
|
+
for (const issue of evaluation.issues) {
|
|
2270
|
+
lines.push(`- [${issue.severity}] ${issue.code}: ${issue.message}`);
|
|
2271
|
+
}
|
|
2272
|
+
lines.push("");
|
|
2273
|
+
}
|
|
2274
|
+
lines.push("## Timeline", "");
|
|
2275
|
+
for (const event of sorted) {
|
|
2276
|
+
lines.push(renderTraceEventMarkdown(event, summary.startedAt));
|
|
2277
|
+
}
|
|
2278
|
+
return lines.join(`
|
|
2279
|
+
`);
|
|
2280
|
+
};
|
|
2281
|
+
|
|
2282
|
+
// src/testing/review.ts
|
|
2283
|
+
var roundMetric = (value) => typeof value === "number" ? Math.round(value * 100) / 100 : undefined;
|
|
2284
|
+
var formatMetric = (label, value, unit = "ms") => typeof value === "number" ? `${label}: ${roundMetric(value)}${unit}` : undefined;
|
|
2285
|
+
var findTimelineEvent = (timeline, event, source) => timeline.find((entry) => entry.event === event && (source === undefined || entry.source === source));
|
|
2286
|
+
var formatTimelineText = (entry) => {
|
|
2287
|
+
const parts = [`- ${entry.atMs}ms`, `[${entry.source}]`, entry.event];
|
|
2288
|
+
if (entry.text) {
|
|
2289
|
+
parts.push(`"${entry.text}"`);
|
|
2290
|
+
}
|
|
2291
|
+
if (entry.reason) {
|
|
2292
|
+
parts.push(`reason=${entry.reason}`);
|
|
2293
|
+
}
|
|
2294
|
+
if (typeof entry.bytes === "number") {
|
|
2295
|
+
parts.push(`bytes=${entry.bytes}`);
|
|
2296
|
+
}
|
|
2297
|
+
if (typeof entry.confidence === "number") {
|
|
2298
|
+
parts.push(`confidence=${roundMetric(entry.confidence)}`);
|
|
2299
|
+
}
|
|
2300
|
+
if (entry.name) {
|
|
2301
|
+
parts.push(`name=${entry.name}`);
|
|
2302
|
+
}
|
|
2303
|
+
return parts.join(" ");
|
|
2304
|
+
};
|
|
2305
|
+
var isLowSignalTimelineEvent = (entry) => entry.event === "inbound-media" || entry.event === "inbound-silence-pad" || entry.event === "stt-send" || entry.event === "tts-audio";
|
|
2306
|
+
var summarizeTimelineTraffic = (timeline) => {
|
|
2307
|
+
const summaries = new Map;
|
|
2308
|
+
for (const entry of timeline) {
|
|
2309
|
+
const label = entry.event === "inbound-media" ? "inbound media chunks" : entry.event === "inbound-silence-pad" ? "inbound silence padding" : entry.event === "stt-send" ? "STT audio sends" : entry.event === "tts-audio" ? "post-first TTS audio chunks" : undefined;
|
|
2310
|
+
if (!label) {
|
|
2311
|
+
continue;
|
|
2312
|
+
}
|
|
2313
|
+
const summary = summaries.get(label) ?? {
|
|
2314
|
+
audioMs: 0,
|
|
2315
|
+
bytes: 0,
|
|
2316
|
+
count: 0,
|
|
2317
|
+
label
|
|
2318
|
+
};
|
|
2319
|
+
summary.count += 1;
|
|
2320
|
+
summary.bytes += typeof entry.bytes === "number" ? entry.bytes : 0;
|
|
2321
|
+
summary.audioMs = (summary.audioMs ?? 0) + (typeof entry.chunkDurationMs === "number" ? entry.chunkDurationMs : 0);
|
|
2322
|
+
summaries.set(label, summary);
|
|
2323
|
+
}
|
|
2324
|
+
return [...summaries.values()];
|
|
2325
|
+
};
|
|
2326
|
+
var compactTimeline = (timeline) => {
|
|
2327
|
+
const rows = [];
|
|
2328
|
+
let index = 0;
|
|
2329
|
+
while (index < timeline.length) {
|
|
2330
|
+
const current = timeline[index];
|
|
2331
|
+
if (!current) {
|
|
2332
|
+
break;
|
|
2333
|
+
}
|
|
2334
|
+
const isBurstEvent = isLowSignalTimelineEvent(current) || current.event === "media" && current.source === "twilio";
|
|
2335
|
+
if (!isBurstEvent) {
|
|
2336
|
+
rows.push(formatTimelineText(current));
|
|
2337
|
+
index += 1;
|
|
2338
|
+
continue;
|
|
2339
|
+
}
|
|
2340
|
+
let endIndex = index;
|
|
2341
|
+
let totalBytes = typeof current.bytes === "number" ? current.bytes : 0;
|
|
2342
|
+
let totalChunkDurationMs = typeof current.chunkDurationMs === "number" ? current.chunkDurationMs : 0;
|
|
2343
|
+
while (endIndex + 1 < timeline.length) {
|
|
2344
|
+
const next = timeline[endIndex + 1];
|
|
2345
|
+
if (!next) {
|
|
2346
|
+
break;
|
|
2347
|
+
}
|
|
2348
|
+
if (next.event !== current.event || next.source !== current.source) {
|
|
2349
|
+
break;
|
|
2350
|
+
}
|
|
2351
|
+
totalBytes += typeof next.bytes === "number" ? next.bytes : 0;
|
|
2352
|
+
totalChunkDurationMs += typeof next.chunkDurationMs === "number" ? next.chunkDurationMs : 0;
|
|
2353
|
+
endIndex += 1;
|
|
2354
|
+
}
|
|
2355
|
+
const startAt = current.atMs;
|
|
2356
|
+
const endAt = timeline[endIndex]?.atMs ?? current.atMs;
|
|
2357
|
+
const count = endIndex - index + 1;
|
|
2358
|
+
const parts = [
|
|
2359
|
+
`- ${startAt}-${endAt}ms`,
|
|
2360
|
+
`[${current.source}]`,
|
|
2361
|
+
`${current.event} x${count}`
|
|
2362
|
+
];
|
|
2363
|
+
if (totalBytes > 0) {
|
|
2364
|
+
parts.push(`bytes=${totalBytes}`);
|
|
2365
|
+
}
|
|
2366
|
+
if (totalChunkDurationMs > 0) {
|
|
2367
|
+
parts.push(`audio=${roundMetric(totalChunkDurationMs)}ms`);
|
|
2368
|
+
}
|
|
2369
|
+
rows.push(parts.join(" "));
|
|
2370
|
+
index = endIndex + 1;
|
|
2371
|
+
}
|
|
2372
|
+
return rows;
|
|
2373
|
+
};
|
|
2374
|
+
var createVoiceCallReviewFromLiveTelephonyReport = (report, options = {}) => {
|
|
2375
|
+
const fixture = report.fixtures?.[0];
|
|
2376
|
+
if (!fixture) {
|
|
2377
|
+
throw new Error("Live telephony review requires at least one fixture result.");
|
|
2378
|
+
}
|
|
2379
|
+
const timeline = [...report.trace ?? []].sort((left, right) => left.atMs - right.atMs);
|
|
2380
|
+
const firstPartial = findTimelineEvent(timeline, "partial", "stt");
|
|
2381
|
+
const commitEvent = findTimelineEvent(timeline, "commit", "turn");
|
|
2382
|
+
const firstTtsAudio = findTimelineEvent(timeline, "tts-first-audio", "benchmark");
|
|
2383
|
+
const firstOutboundMedia = findTimelineEvent(timeline, "media", "twilio");
|
|
2384
|
+
const bargeInEvent = findTimelineEvent(timeline, "barge-in", "benchmark");
|
|
2385
|
+
const clearEvent = findTimelineEvent(timeline, "clear", "twilio");
|
|
2386
|
+
const lastSttText = [...timeline].reverse().find((entry) => entry.source === "stt" && (entry.event === "partial" || entry.event === "final") && typeof entry.text === "string" && entry.text.length > 0)?.text ?? undefined;
|
|
2387
|
+
const latencyBreakdown = [
|
|
2388
|
+
typeof firstPartial?.atMs === "number" ? {
|
|
2389
|
+
label: "start to first partial",
|
|
2390
|
+
valueMs: firstPartial.atMs
|
|
2391
|
+
} : undefined,
|
|
2392
|
+
typeof firstPartial?.atMs === "number" && typeof commitEvent?.atMs === "number" ? {
|
|
2393
|
+
label: "first partial to commit",
|
|
2394
|
+
valueMs: commitEvent.atMs - firstPartial.atMs
|
|
2395
|
+
} : undefined,
|
|
2396
|
+
typeof commitEvent?.atMs === "number" && typeof firstTtsAudio?.atMs === "number" ? {
|
|
2397
|
+
label: "commit to first TTS audio",
|
|
2398
|
+
valueMs: firstTtsAudio.atMs - commitEvent.atMs
|
|
2399
|
+
} : undefined,
|
|
2400
|
+
typeof commitEvent?.atMs === "number" && typeof firstOutboundMedia?.atMs === "number" ? {
|
|
2401
|
+
label: "commit to first outbound media",
|
|
2402
|
+
valueMs: firstOutboundMedia.atMs - commitEvent.atMs
|
|
2403
|
+
} : undefined,
|
|
2404
|
+
typeof bargeInEvent?.atMs === "number" && typeof clearEvent?.atMs === "number" ? {
|
|
2405
|
+
label: "barge-in to clear",
|
|
2406
|
+
valueMs: clearEvent.atMs - bargeInEvent.atMs
|
|
2407
|
+
} : undefined
|
|
2408
|
+
].filter((value) => value !== undefined && value.valueMs >= 0);
|
|
2409
|
+
const notes = [
|
|
2410
|
+
report.variant?.description,
|
|
2411
|
+
firstPartial?.text ? `First partial: "${firstPartial.text}"` : undefined,
|
|
2412
|
+
lastSttText ? `Last STT text: "${lastSttText}"` : undefined
|
|
2413
|
+
].filter((value) => typeof value === "string" && value.length > 0);
|
|
2414
|
+
return {
|
|
2415
|
+
config: {
|
|
2416
|
+
preset: options.preset,
|
|
2417
|
+
stt: report.variant ? {
|
|
2418
|
+
description: report.variant.description,
|
|
2419
|
+
id: report.variant.id,
|
|
2420
|
+
model: report.variant.model
|
|
2421
|
+
} : undefined,
|
|
2422
|
+
tts: report.ttsConfig,
|
|
2423
|
+
turnDetection: report.turnDetectionConfig
|
|
2424
|
+
},
|
|
2425
|
+
errors: fixture.errors ?? [],
|
|
2426
|
+
expectedText: fixture.expectedText,
|
|
2427
|
+
fixtureId: fixture.fixtureId,
|
|
2428
|
+
generatedAt: report.generatedAt,
|
|
2429
|
+
latencyBreakdown,
|
|
2430
|
+
notes,
|
|
2431
|
+
path: options.path,
|
|
2432
|
+
summary: {
|
|
2433
|
+
clearLatencyMs: roundMetric(fixture.clearLatencyMs),
|
|
2434
|
+
elapsedMs: roundMetric(fixture.elapsedMs),
|
|
2435
|
+
firstOutboundMediaLatencyMs: roundMetric(fixture.firstOutboundMediaLatencyMs),
|
|
2436
|
+
firstTurnLatencyMs: roundMetric(fixture.firstTurnLatencyMs),
|
|
2437
|
+
markLatencyMs: roundMetric(fixture.markLatencyMs),
|
|
2438
|
+
outboundMediaCount: fixture.outboundMediaCount,
|
|
2439
|
+
pass: fixture.passes,
|
|
2440
|
+
termRecall: roundMetric(fixture.termRecall),
|
|
2441
|
+
turnCount: fixture.turnCount,
|
|
2442
|
+
wordErrorRate: roundMetric(fixture.wordErrorRate)
|
|
2443
|
+
},
|
|
2444
|
+
title: fixture.title ?? "Voice Call Review",
|
|
2445
|
+
timeline,
|
|
2446
|
+
transcript: {
|
|
2447
|
+
actual: fixture.actualText,
|
|
2448
|
+
expected: fixture.expectedText
|
|
2449
|
+
}
|
|
2450
|
+
};
|
|
2451
|
+
};
|
|
2452
|
+
var withVoiceCallReviewId = (id, artifact) => ({
|
|
2453
|
+
...artifact,
|
|
2454
|
+
id
|
|
2455
|
+
});
|
|
2456
|
+
var toErrorMessage = (error) => {
|
|
2457
|
+
if (typeof error === "string" && error.trim().length > 0) {
|
|
2458
|
+
return error;
|
|
2459
|
+
}
|
|
2460
|
+
if (error instanceof Error && error.message.trim().length > 0) {
|
|
2461
|
+
return error.message;
|
|
2462
|
+
}
|
|
2463
|
+
return "Unknown call error";
|
|
2464
|
+
};
|
|
2465
|
+
var createVoiceCallReviewRecorder = (options = {}) => {
|
|
2466
|
+
const now = options.now ?? (() => Date.now());
|
|
2467
|
+
const startedAt = now();
|
|
2468
|
+
const errors = [];
|
|
2469
|
+
const timeline = [];
|
|
2470
|
+
const committedTurns = [];
|
|
2471
|
+
const committedTurnIds = new Set;
|
|
2472
|
+
const push = (source, event, fields = {}) => {
|
|
2473
|
+
timeline.push({
|
|
2474
|
+
atMs: Math.max(0, now() - startedAt),
|
|
2475
|
+
event,
|
|
2476
|
+
source,
|
|
2477
|
+
...fields
|
|
2478
|
+
});
|
|
2479
|
+
};
|
|
2480
|
+
return {
|
|
2481
|
+
finalize: () => {
|
|
2482
|
+
const sortedTimeline = [...timeline].sort((left, right) => left.atMs - right.atMs);
|
|
2483
|
+
const firstPartial = findTimelineEvent(sortedTimeline, "partial", "stt");
|
|
2484
|
+
const commitEvent = findTimelineEvent(sortedTimeline, "commit", "turn");
|
|
2485
|
+
const firstTtsAudio = findTimelineEvent(sortedTimeline, "tts-first-audio", "benchmark");
|
|
2486
|
+
const firstOutboundMedia = findTimelineEvent(sortedTimeline, "media", "twilio");
|
|
2487
|
+
const bargeInEvent = findTimelineEvent(sortedTimeline, "barge-in", "benchmark");
|
|
2488
|
+
const clearEvent = findTimelineEvent(sortedTimeline, "clear", "twilio");
|
|
2489
|
+
const markEvent = findTimelineEvent(sortedTimeline, "mark", "twilio");
|
|
2490
|
+
const elapsedMs = sortedTimeline.at(-1)?.atMs ?? 0;
|
|
2491
|
+
const lastSttText = [...sortedTimeline].reverse().find((entry) => entry.source === "stt" && (entry.event === "partial" || entry.event === "final") && typeof entry.text === "string" && entry.text.length > 0)?.text ?? undefined;
|
|
2492
|
+
const latencyBreakdown = [
|
|
2493
|
+
typeof firstPartial?.atMs === "number" ? {
|
|
2494
|
+
label: "start to first partial",
|
|
2495
|
+
valueMs: firstPartial.atMs
|
|
2496
|
+
} : undefined,
|
|
2497
|
+
typeof firstPartial?.atMs === "number" && typeof commitEvent?.atMs === "number" ? {
|
|
2498
|
+
label: "first partial to commit",
|
|
2499
|
+
valueMs: commitEvent.atMs - firstPartial.atMs
|
|
2500
|
+
} : undefined,
|
|
2501
|
+
typeof commitEvent?.atMs === "number" && typeof firstTtsAudio?.atMs === "number" ? {
|
|
2502
|
+
label: "commit to first TTS audio",
|
|
2503
|
+
valueMs: firstTtsAudio.atMs - commitEvent.atMs
|
|
2504
|
+
} : undefined,
|
|
2505
|
+
typeof commitEvent?.atMs === "number" && typeof firstOutboundMedia?.atMs === "number" ? {
|
|
2506
|
+
label: "commit to first outbound media",
|
|
2507
|
+
valueMs: firstOutboundMedia.atMs - commitEvent.atMs
|
|
2508
|
+
} : undefined,
|
|
2509
|
+
typeof bargeInEvent?.atMs === "number" && typeof clearEvent?.atMs === "number" ? {
|
|
2510
|
+
label: "barge-in to clear",
|
|
2511
|
+
valueMs: clearEvent.atMs - bargeInEvent.atMs
|
|
2512
|
+
} : undefined
|
|
2513
|
+
].filter((value) => value !== undefined && value.valueMs >= 0);
|
|
2514
|
+
return {
|
|
2515
|
+
config: options.config,
|
|
2516
|
+
errors,
|
|
2517
|
+
fixtureId: options.fixtureId,
|
|
2518
|
+
generatedAt: now(),
|
|
2519
|
+
latencyBreakdown,
|
|
2520
|
+
notes: [
|
|
2521
|
+
firstPartial?.text ? `First partial: "${firstPartial.text}"` : undefined,
|
|
2522
|
+
lastSttText ? `Last STT text: "${lastSttText}"` : undefined
|
|
2523
|
+
].filter((value) => typeof value === "string"),
|
|
2524
|
+
path: options.path,
|
|
2525
|
+
summary: {
|
|
2526
|
+
clearLatencyMs: roundMetric(typeof clearEvent?.atMs === "number" && typeof bargeInEvent?.atMs === "number" ? clearEvent.atMs - bargeInEvent.atMs : undefined),
|
|
2527
|
+
elapsedMs: roundMetric(elapsedMs),
|
|
2528
|
+
firstOutboundMediaLatencyMs: roundMetric(firstOutboundMedia?.atMs),
|
|
2529
|
+
firstTurnLatencyMs: roundMetric(commitEvent?.atMs),
|
|
2530
|
+
markLatencyMs: roundMetric(markEvent?.atMs),
|
|
2531
|
+
outboundMediaCount: sortedTimeline.filter((entry) => entry.source === "twilio" && entry.event === "media").length,
|
|
2532
|
+
pass: errors.length === 0,
|
|
2533
|
+
turnCount: committedTurns.length
|
|
2534
|
+
},
|
|
2535
|
+
title: options.title ?? "Voice Call Review",
|
|
2536
|
+
timeline: sortedTimeline,
|
|
2537
|
+
transcript: {
|
|
2538
|
+
actual: committedTurns.join(" ").trim()
|
|
2539
|
+
}
|
|
2540
|
+
};
|
|
2541
|
+
},
|
|
2542
|
+
recordError: (error) => {
|
|
2543
|
+
const message = toErrorMessage(error);
|
|
2544
|
+
errors.push(message);
|
|
2545
|
+
push("turn", "error", {
|
|
2546
|
+
reason: message
|
|
2547
|
+
});
|
|
2548
|
+
},
|
|
2549
|
+
recordTwilioInbound: (input) => {
|
|
2550
|
+
push("twilio", input.event, {
|
|
2551
|
+
bytes: input.bytes,
|
|
2552
|
+
chunkDurationMs: input.chunkDurationMs,
|
|
2553
|
+
name: input.name,
|
|
2554
|
+
reason: input.reason,
|
|
2555
|
+
text: input.text,
|
|
2556
|
+
track: input.track
|
|
2557
|
+
});
|
|
2558
|
+
},
|
|
2559
|
+
recordTwilioOutbound: (input) => {
|
|
2560
|
+
push("twilio", input.event, {
|
|
2561
|
+
bytes: input.bytes,
|
|
2562
|
+
chunkDurationMs: input.chunkDurationMs,
|
|
2563
|
+
name: input.name,
|
|
2564
|
+
reason: input.reason,
|
|
2565
|
+
text: input.text,
|
|
2566
|
+
track: input.track
|
|
2567
|
+
});
|
|
2568
|
+
},
|
|
2569
|
+
recordVoiceMessage: (message) => {
|
|
2570
|
+
switch (message.type) {
|
|
2571
|
+
case "partial":
|
|
2572
|
+
case "final":
|
|
2573
|
+
push("stt", message.type, {
|
|
2574
|
+
confidence: message.transcript.confidence,
|
|
2575
|
+
text: message.transcript.text
|
|
2576
|
+
});
|
|
2577
|
+
return;
|
|
2578
|
+
case "assistant":
|
|
2579
|
+
push("turn", "assistant", {
|
|
2580
|
+
text: message.text
|
|
2581
|
+
});
|
|
2582
|
+
return;
|
|
2583
|
+
case "audio":
|
|
2584
|
+
push("benchmark", timeline.some((entry) => entry.event === "tts-first-audio") ? "tts-audio" : "tts-first-audio", {
|
|
2585
|
+
bytes: Math.floor(message.chunkBase64.length * 3 / 4)
|
|
2586
|
+
});
|
|
2587
|
+
return;
|
|
2588
|
+
case "turn":
|
|
2589
|
+
if (committedTurnIds.has(message.turn.id)) {
|
|
2590
|
+
return;
|
|
2591
|
+
}
|
|
2592
|
+
committedTurnIds.add(message.turn.id);
|
|
2593
|
+
committedTurns.push(message.turn.text);
|
|
2594
|
+
push("turn", "commit", {
|
|
2595
|
+
confidence: message.turn.quality?.averageConfidence,
|
|
2596
|
+
text: message.turn.text
|
|
2597
|
+
});
|
|
2598
|
+
return;
|
|
2599
|
+
case "error":
|
|
2600
|
+
errors.push(message.message);
|
|
2601
|
+
push("turn", "error", {
|
|
2602
|
+
reason: message.message
|
|
2603
|
+
});
|
|
2604
|
+
return;
|
|
2605
|
+
case "complete":
|
|
2606
|
+
push("turn", "complete", {
|
|
2607
|
+
text: message.sessionId
|
|
2608
|
+
});
|
|
2609
|
+
return;
|
|
2610
|
+
case "session":
|
|
2611
|
+
push("turn", "session", {
|
|
2612
|
+
reason: message.status,
|
|
2613
|
+
text: message.sessionId
|
|
2614
|
+
});
|
|
2615
|
+
return;
|
|
2616
|
+
case "pong":
|
|
2617
|
+
push("benchmark", "pong");
|
|
2618
|
+
}
|
|
2619
|
+
}
|
|
2620
|
+
};
|
|
2621
|
+
};
|
|
2622
|
+
var renderConfigSection = (config) => {
|
|
2623
|
+
if (!config) {
|
|
2624
|
+
return "";
|
|
2625
|
+
}
|
|
2626
|
+
return [
|
|
2627
|
+
"## Config",
|
|
2628
|
+
"",
|
|
2629
|
+
"```json",
|
|
2630
|
+
JSON.stringify(config, null, 2),
|
|
2631
|
+
"```"
|
|
2632
|
+
].join(`
|
|
2633
|
+
`);
|
|
2634
|
+
};
|
|
2635
|
+
var renderTimeline = (timeline) => {
|
|
2636
|
+
const focusedTimeline = timeline.filter((entry) => !isLowSignalTimelineEvent(entry));
|
|
2637
|
+
if (focusedTimeline.length === 0) {
|
|
2638
|
+
return `## Timeline
|
|
2639
|
+
|
|
2640
|
+
_No timeline events captured._`;
|
|
2641
|
+
}
|
|
2642
|
+
const lines = compactTimeline(focusedTimeline);
|
|
2643
|
+
return ["## Timeline", "", ...lines].join(`
|
|
2644
|
+
`);
|
|
2645
|
+
};
|
|
2646
|
+
var renderTransportSummary = (timeline) => {
|
|
2647
|
+
const summaries = summarizeTimelineTraffic(timeline);
|
|
2648
|
+
if (summaries.length === 0) {
|
|
2649
|
+
return "";
|
|
2650
|
+
}
|
|
2651
|
+
return [
|
|
2652
|
+
"## Transport Summary",
|
|
2653
|
+
"",
|
|
2654
|
+
...summaries.map((summary) => {
|
|
2655
|
+
const parts = [`- ${summary.label}: ${summary.count}`];
|
|
2656
|
+
if (summary.bytes > 0) {
|
|
2657
|
+
parts.push(`${summary.bytes} bytes`);
|
|
2658
|
+
}
|
|
2659
|
+
if ((summary.audioMs ?? 0) > 0) {
|
|
2660
|
+
parts.push(`${roundMetric(summary.audioMs)}ms audio`);
|
|
2661
|
+
}
|
|
2662
|
+
return parts.join(", ");
|
|
2663
|
+
})
|
|
2664
|
+
].join(`
|
|
2665
|
+
`);
|
|
2666
|
+
};
|
|
2667
|
+
var renderLatencyBreakdown = (breakdown) => {
|
|
2668
|
+
if (breakdown.length === 0) {
|
|
2669
|
+
return "";
|
|
2670
|
+
}
|
|
2671
|
+
return [
|
|
2672
|
+
"## Latency Breakdown",
|
|
2673
|
+
"",
|
|
2674
|
+
...breakdown.map((entry) => `- ${entry.label}: ${roundMetric(entry.valueMs)}ms`)
|
|
2675
|
+
].join(`
|
|
2676
|
+
`);
|
|
2677
|
+
};
|
|
2678
|
+
var renderVoiceCallReviewHTML = (artifact) => {
|
|
2679
|
+
const notes = artifact.notes.map((note) => `<li>${escapeHtml(note)}</li>`).join("");
|
|
2680
|
+
const latency = artifact.latencyBreakdown.map((entry) => `<li><strong>${escapeHtml(entry.label)}:</strong> ${roundMetric(entry.valueMs)}ms</li>`).join("");
|
|
2681
|
+
const transport = summarizeTimelineTraffic(artifact.timeline).map((summary) => {
|
|
2682
|
+
const parts = [`${summary.count}`, "events"];
|
|
2683
|
+
if (summary.bytes > 0) {
|
|
2684
|
+
parts.push(`${summary.bytes} bytes`);
|
|
2685
|
+
}
|
|
2686
|
+
if ((summary.audioMs ?? 0) > 0) {
|
|
2687
|
+
parts.push(`${roundMetric(summary.audioMs)}ms audio`);
|
|
2688
|
+
}
|
|
2689
|
+
return `<li><strong>${escapeHtml(summary.label)}:</strong> ${escapeHtml(parts.join(", "))}</li>`;
|
|
2690
|
+
}).join("");
|
|
2691
|
+
const timeline = compactTimeline(artifact.timeline.filter((entry) => !isLowSignalTimelineEvent(entry))).map((line) => `<li>${escapeHtml(line.replace(/^- /u, ""))}</li>`).join("");
|
|
2692
|
+
return `<!doctype html>
|
|
2693
|
+
<html lang="en">
|
|
2694
|
+
<head>
|
|
2695
|
+
<meta charset="utf-8" />
|
|
2696
|
+
<meta name="viewport" content="width=device-width, initial-scale=1" />
|
|
2697
|
+
<title>${escapeHtml(artifact.title)}</title>
|
|
2698
|
+
<style>
|
|
2699
|
+
:root { color-scheme: dark; }
|
|
2700
|
+
body { font-family: ui-sans-serif, system-ui, sans-serif; margin: 0; padding: 24px; background: #0b0d10; color: #f4f4f5; }
|
|
2701
|
+
main { max-width: 980px; margin: 0 auto; display: grid; gap: 16px; }
|
|
2702
|
+
section { background: #13161b; border: 1px solid #232833; border-radius: 16px; padding: 18px; }
|
|
2703
|
+
h1, h2 { margin: 0 0 12px; }
|
|
2704
|
+
ul { margin: 0; padding-left: 20px; display: grid; gap: 8px; }
|
|
2705
|
+
code, pre { font-family: ui-monospace, SFMono-Regular, monospace; }
|
|
2706
|
+
pre { white-space: pre-wrap; overflow-wrap: anywhere; background: #0f1217; border-radius: 12px; padding: 14px; border: 1px solid #232833; }
|
|
2707
|
+
.grid { display: grid; gap: 16px; grid-template-columns: repeat(auto-fit, minmax(260px, 1fr)); }
|
|
2708
|
+
.metric { display: grid; gap: 4px; }
|
|
2709
|
+
.label { color: #a1a1aa; font-size: 0.82rem; text-transform: uppercase; letter-spacing: 0.08em; }
|
|
2710
|
+
.value { font-size: 1.05rem; }
|
|
2711
|
+
</style>
|
|
2712
|
+
</head>
|
|
2713
|
+
<body>
|
|
2714
|
+
<main>
|
|
2715
|
+
<section>
|
|
2716
|
+
<h1>${escapeHtml(artifact.title)}</h1>
|
|
2717
|
+
<div class="grid">
|
|
2718
|
+
<div class="metric"><div class="label">Pass</div><div class="value">${artifact.summary.pass ? "yes" : "no"}</div></div>
|
|
2719
|
+
<div class="metric"><div class="label">First Turn</div><div class="value">${artifact.summary.firstTurnLatencyMs ?? "n/a"}ms</div></div>
|
|
2720
|
+
<div class="metric"><div class="label">First Outbound Media</div><div class="value">${artifact.summary.firstOutboundMediaLatencyMs ?? "n/a"}ms</div></div>
|
|
2721
|
+
<div class="metric"><div class="label">Turn Count</div><div class="value">${artifact.summary.turnCount ?? "n/a"}</div></div>
|
|
2722
|
+
</div>
|
|
2723
|
+
</section>
|
|
2724
|
+
<section>
|
|
2725
|
+
<h2>Transcript</h2>
|
|
2726
|
+
<ul>
|
|
2727
|
+
<li><strong>Expected:</strong> ${escapeHtml(artifact.transcript.expected ?? "n/a")}</li>
|
|
2728
|
+
<li><strong>Actual:</strong> ${escapeHtml(artifact.transcript.actual || "n/a")}</li>
|
|
2729
|
+
</ul>
|
|
2730
|
+
</section>
|
|
2731
|
+
<section>
|
|
2732
|
+
<h2>Notes</h2>
|
|
2733
|
+
<ul>${notes || "<li>No notes.</li>"}</ul>
|
|
2734
|
+
</section>
|
|
2735
|
+
<section>
|
|
2736
|
+
<h2>Latency Breakdown</h2>
|
|
2737
|
+
<ul>${latency || "<li>No latency data.</li>"}</ul>
|
|
2738
|
+
</section>
|
|
2739
|
+
<section>
|
|
2740
|
+
<h2>Transport Summary</h2>
|
|
2741
|
+
<ul>${transport || "<li>No transport data.</li>"}</ul>
|
|
2742
|
+
</section>
|
|
2743
|
+
<section>
|
|
2744
|
+
<h2>Timeline</h2>
|
|
2745
|
+
<ul>${timeline || "<li>No timeline events.</li>"}</ul>
|
|
2746
|
+
</section>
|
|
2747
|
+
<section>
|
|
2748
|
+
<h2>Config</h2>
|
|
2749
|
+
<pre>${escapeHtml(JSON.stringify(artifact.config ?? {}, null, 2))}</pre>
|
|
2750
|
+
</section>
|
|
2751
|
+
</main>
|
|
2752
|
+
</body>
|
|
2753
|
+
</html>`;
|
|
2754
|
+
};
|
|
2755
|
+
var renderVoiceCallReviewMarkdown = (artifact) => {
|
|
2756
|
+
const summaryLines = [
|
|
2757
|
+
`- pass: ${artifact.summary.pass ? "yes" : "no"}`,
|
|
2758
|
+
formatMetric("first turn", artifact.summary.firstTurnLatencyMs),
|
|
2759
|
+
formatMetric("first outbound media", artifact.summary.firstOutboundMediaLatencyMs),
|
|
2760
|
+
formatMetric("mark", artifact.summary.markLatencyMs),
|
|
2761
|
+
formatMetric("clear", artifact.summary.clearLatencyMs),
|
|
2762
|
+
formatMetric("elapsed", artifact.summary.elapsedMs),
|
|
2763
|
+
typeof artifact.summary.wordErrorRate === "number" ? `- word error rate: ${artifact.summary.wordErrorRate}` : undefined,
|
|
2764
|
+
typeof artifact.summary.termRecall === "number" ? `- term recall: ${artifact.summary.termRecall}` : undefined,
|
|
2765
|
+
typeof artifact.summary.turnCount === "number" ? `- turn count: ${artifact.summary.turnCount}` : undefined,
|
|
2766
|
+
typeof artifact.summary.outboundMediaCount === "number" ? `- outbound media count: ${artifact.summary.outboundMediaCount}` : undefined
|
|
2767
|
+
].filter((value) => typeof value === "string");
|
|
2768
|
+
const notes = artifact.notes.length ? ["## Notes", "", ...artifact.notes.map((note) => `- ${note}`)].join(`
|
|
2769
|
+
`) : "";
|
|
2770
|
+
const errors = artifact.errors.length ? ["## Errors", "", ...artifact.errors.map((error) => `- ${error}`)].join(`
|
|
2771
|
+
`) : "";
|
|
2772
|
+
const latency = renderLatencyBreakdown(artifact.latencyBreakdown);
|
|
2773
|
+
const transportSummary = renderTransportSummary(artifact.timeline);
|
|
2774
|
+
return [
|
|
2775
|
+
`# ${artifact.title}`,
|
|
2776
|
+
"",
|
|
2777
|
+
artifact.path ? `Source: \`${artifact.path}\`` : undefined,
|
|
2778
|
+
artifact.fixtureId ? `Fixture: \`${artifact.fixtureId}\`` : undefined,
|
|
2779
|
+
"",
|
|
2780
|
+
"## Summary",
|
|
2781
|
+
"",
|
|
2782
|
+
...summaryLines,
|
|
2783
|
+
"",
|
|
2784
|
+
"## Transcript",
|
|
2785
|
+
"",
|
|
2786
|
+
`- expected: ${artifact.transcript.expected ?? "_n/a_"}`,
|
|
2787
|
+
`- actual: ${artifact.transcript.actual}`,
|
|
2788
|
+
"",
|
|
2789
|
+
notes,
|
|
2790
|
+
notes ? "" : undefined,
|
|
2791
|
+
latency,
|
|
2792
|
+
latency ? "" : undefined,
|
|
2793
|
+
transportSummary,
|
|
2794
|
+
transportSummary ? "" : undefined,
|
|
2795
|
+
errors,
|
|
2796
|
+
errors ? "" : undefined,
|
|
2797
|
+
renderConfigSection(artifact.config),
|
|
2798
|
+
renderConfigSection(artifact.config) ? "" : undefined,
|
|
2799
|
+
renderTimeline(artifact.timeline)
|
|
2800
|
+
].filter((value) => typeof value === "string").join(`
|
|
2801
|
+
`);
|
|
2802
|
+
};
|
|
2803
|
+
|
|
2804
|
+
// src/drizzle/runtimeStorage.ts
|
|
2805
|
+
var voiceAuditDeliveriesTable = voiceDocumentTable("voice_audit_deliveries");
|
|
2806
|
+
var voiceAuditTable = voiceDocumentTable("voice_audit");
|
|
2807
|
+
var voiceCampaignsTable = voiceDocumentTable("voice_campaigns");
|
|
2808
|
+
var voiceEventsTable = voiceDocumentTable("voice_events");
|
|
2809
|
+
var voiceExternalObjectsTable = voiceDocumentTable("voice_external_objects");
|
|
2810
|
+
var voiceReviewsTable = voiceDocumentTable("voice_reviews");
|
|
2811
|
+
var voiceSessionsTable = voiceDocumentTable("voice_sessions");
|
|
2812
|
+
var voiceTasksTable = voiceDocumentTable("voice_tasks");
|
|
2813
|
+
var voiceTelephonyWebhookIdempotencyTable = voiceDocumentTable("voice_telephony_webhook_idempotency");
|
|
2814
|
+
var voiceTraceDeliveriesTable = voiceDocumentTable("voice_trace_deliveries");
|
|
2815
|
+
var voiceTracesTable = voiceDocumentTable("voice_traces");
|
|
2816
|
+
var voiceRuntimeStorageDrizzleSchema = {
|
|
2817
|
+
voiceAudit: voiceAuditTable,
|
|
2818
|
+
voiceAuditDeliveries: voiceAuditDeliveriesTable,
|
|
2819
|
+
voiceCampaigns: voiceCampaignsTable,
|
|
2820
|
+
voiceEvents: voiceEventsTable,
|
|
2821
|
+
voiceExternalObjects: voiceExternalObjectsTable,
|
|
2822
|
+
voiceReviews: voiceReviewsTable,
|
|
2823
|
+
voiceSessions: voiceSessionsTable,
|
|
2824
|
+
voiceTasks: voiceTasksTable,
|
|
2825
|
+
voiceTelephonyWebhookIdempotency: voiceTelephonyWebhookIdempotencyTable,
|
|
2826
|
+
voiceTraceDeliveries: voiceTraceDeliveriesTable,
|
|
2827
|
+
voiceTraces: voiceTracesTable
|
|
2828
|
+
};
|
|
2829
|
+
var createDrizzleSessionStore = (db) => {
|
|
2830
|
+
const store = createVoiceDrizzleRecordStore({
|
|
2831
|
+
db,
|
|
2832
|
+
decorate: (_id, value) => value,
|
|
2833
|
+
getSortAt: (value) => value.lastActivityAt ?? value.createdAt,
|
|
2834
|
+
table: voiceSessionsTable
|
|
2835
|
+
});
|
|
2836
|
+
const getOrCreate = async (id) => {
|
|
2837
|
+
const existing = await store.get(id);
|
|
2838
|
+
if (existing) {
|
|
2839
|
+
return existing;
|
|
2840
|
+
}
|
|
2841
|
+
const session = createVoiceSessionRecord(id);
|
|
2842
|
+
await store.set(id, session);
|
|
2843
|
+
return session;
|
|
2844
|
+
};
|
|
2845
|
+
return {
|
|
2846
|
+
get: store.get,
|
|
2847
|
+
getOrCreate,
|
|
2848
|
+
remove: store.remove,
|
|
2849
|
+
set: store.set,
|
|
2850
|
+
list: async () => (await store.list()).map((session) => toVoiceSessionSummary(session)).sort((first, second) => (second.lastActivityAt ?? second.createdAt) - (first.lastActivityAt ?? first.createdAt))
|
|
2851
|
+
};
|
|
2852
|
+
};
|
|
2853
|
+
var createDrizzleReviewStore = (db) => createVoiceDrizzleRecordStore({
|
|
2854
|
+
db,
|
|
2855
|
+
decorate: (id, value) => withVoiceCallReviewId(id, value),
|
|
2856
|
+
getSortAt: (value) => value.generatedAt ?? 0,
|
|
2857
|
+
table: voiceReviewsTable
|
|
2858
|
+
});
|
|
2859
|
+
var createDrizzleTaskStore = (db) => createVoiceDrizzleRecordStore({
|
|
2860
|
+
db,
|
|
2861
|
+
decorate: (id, value) => withVoiceOpsTaskId(id, value),
|
|
2862
|
+
getSortAt: (value) => value.createdAt,
|
|
2863
|
+
table: voiceTasksTable
|
|
2864
|
+
});
|
|
2865
|
+
var createDrizzleEventStore = (db) => createVoiceDrizzleRecordStore({
|
|
2866
|
+
db,
|
|
2867
|
+
decorate: (id, value) => withVoiceIntegrationEventId(id, value),
|
|
2868
|
+
getSortAt: (value) => value.createdAt,
|
|
2869
|
+
table: voiceEventsTable
|
|
2870
|
+
});
|
|
2871
|
+
var createDrizzleExternalObjectMapStore = (db) => {
|
|
2872
|
+
const store = createVoiceDrizzleRecordStore({
|
|
2873
|
+
db,
|
|
2874
|
+
decorate: (id, value) => ({
|
|
2875
|
+
...value,
|
|
2876
|
+
id
|
|
2877
|
+
}),
|
|
2878
|
+
getSortAt: (value) => value.updatedAt,
|
|
2879
|
+
table: voiceExternalObjectsTable
|
|
2880
|
+
});
|
|
2881
|
+
const find = async (input) => (await store.list()).find((mapping) => mapping.provider === input.provider && mapping.sourceId === input.sourceId && (input.sinkId === undefined || mapping.sinkId === input.sinkId) && (input.sourceType === undefined || mapping.sourceType === input.sourceType));
|
|
2882
|
+
return {
|
|
2883
|
+
...store,
|
|
2884
|
+
find
|
|
2885
|
+
};
|
|
2886
|
+
};
|
|
2887
|
+
var createDrizzleTraceEventStore = (db) => {
|
|
2888
|
+
const store = createVoiceDrizzleRecordStore({
|
|
2889
|
+
db,
|
|
2890
|
+
decorate: (_id, value) => value,
|
|
2891
|
+
getSortAt: (value) => value.at,
|
|
2892
|
+
table: voiceTracesTable
|
|
2893
|
+
});
|
|
2894
|
+
const append = async (event) => {
|
|
2895
|
+
const stored = createVoiceTraceEvent(event);
|
|
2896
|
+
await store.set(stored.id, stored);
|
|
2897
|
+
return stored;
|
|
2898
|
+
};
|
|
2899
|
+
return {
|
|
2900
|
+
append,
|
|
2901
|
+
get: store.get,
|
|
2902
|
+
remove: store.remove,
|
|
2903
|
+
list: async (filter) => filterVoiceTraceEvents(await store.list(), filter)
|
|
2904
|
+
};
|
|
2905
|
+
};
|
|
2906
|
+
var createDrizzleTraceSinkDeliveryStore = (db) => createVoiceDrizzleRecordStore({
|
|
2907
|
+
db,
|
|
2908
|
+
decorate: (id, value) => ({
|
|
2909
|
+
...value,
|
|
2910
|
+
id
|
|
2911
|
+
}),
|
|
2912
|
+
getSortAt: (value) => value.createdAt,
|
|
2913
|
+
table: voiceTraceDeliveriesTable
|
|
2914
|
+
});
|
|
2915
|
+
var createDrizzleAuditEventStore = (db) => {
|
|
2916
|
+
const store = createVoiceDrizzleRecordStore({
|
|
2917
|
+
db,
|
|
2918
|
+
decorate: (_id, value) => value,
|
|
2919
|
+
getSortAt: (value) => value.at,
|
|
2920
|
+
table: voiceAuditTable
|
|
2921
|
+
});
|
|
2922
|
+
const append = async (event) => {
|
|
2923
|
+
const stored = createVoiceAuditEvent(event);
|
|
2924
|
+
await store.set(stored.id, stored);
|
|
2925
|
+
return stored;
|
|
2926
|
+
};
|
|
2927
|
+
return {
|
|
2928
|
+
append,
|
|
2929
|
+
get: store.get,
|
|
2930
|
+
list: async (filter) => filterVoiceAuditEvents(await store.list(), filter)
|
|
2931
|
+
};
|
|
2932
|
+
};
|
|
2933
|
+
var createDrizzleAuditSinkDeliveryStore = (db) => createVoiceDrizzleRecordStore({
|
|
2934
|
+
db,
|
|
2935
|
+
decorate: (id, value) => ({
|
|
2936
|
+
...value,
|
|
2937
|
+
id
|
|
2938
|
+
}),
|
|
2939
|
+
getSortAt: (value) => value.createdAt,
|
|
2940
|
+
table: voiceAuditDeliveriesTable
|
|
2941
|
+
});
|
|
2942
|
+
var createDrizzleTelephonyWebhookIdempotencyStore = (db) => createVoiceDrizzleRecordStore({
|
|
2943
|
+
db,
|
|
2944
|
+
decorate: (_id, value) => value,
|
|
2945
|
+
getSortAt: (value) => value.updatedAt,
|
|
2946
|
+
table: voiceTelephonyWebhookIdempotencyTable
|
|
2947
|
+
});
|
|
2948
|
+
var createDrizzleCampaignStore = (db) => createVoiceDrizzleRecordStore({
|
|
2949
|
+
db,
|
|
2950
|
+
decorate: (_id, value) => value,
|
|
2951
|
+
getSortAt: (value) => value.campaign.createdAt,
|
|
2952
|
+
table: voiceCampaignsTable
|
|
2953
|
+
});
|
|
2954
|
+
var createVoiceDrizzleAuditEventStore = (options) => createDrizzleAuditEventStore(options.db);
|
|
2955
|
+
var createVoiceDrizzleAuditSinkDeliveryStore = (options) => createDrizzleAuditSinkDeliveryStore(options.db);
|
|
2956
|
+
var createVoiceDrizzleCampaignStore = (options) => createDrizzleCampaignStore(options.db);
|
|
2957
|
+
var createVoiceDrizzleExternalObjectMapStore = (options) => createDrizzleExternalObjectMapStore(options.db);
|
|
2958
|
+
var createVoiceDrizzleIntegrationEventStore = (options) => createDrizzleEventStore(options.db);
|
|
2959
|
+
var createVoiceDrizzleReviewStore = (options) => createDrizzleReviewStore(options.db);
|
|
2960
|
+
var createVoiceDrizzleRuntimeStorage = (options) => ({
|
|
2961
|
+
audit: createDrizzleAuditEventStore(options.db),
|
|
2962
|
+
auditDeliveries: createDrizzleAuditSinkDeliveryStore(options.db),
|
|
2963
|
+
campaigns: createDrizzleCampaignStore(options.db),
|
|
2964
|
+
events: createDrizzleEventStore(options.db),
|
|
2965
|
+
externalObjects: createDrizzleExternalObjectMapStore(options.db),
|
|
2966
|
+
reviews: createDrizzleReviewStore(options.db),
|
|
2967
|
+
session: createDrizzleSessionStore(options.db),
|
|
2968
|
+
tasks: createDrizzleTaskStore(options.db),
|
|
2969
|
+
traceDeliveries: createDrizzleTraceSinkDeliveryStore(options.db),
|
|
2970
|
+
traces: createDrizzleTraceEventStore(options.db)
|
|
2971
|
+
});
|
|
2972
|
+
var createVoiceDrizzleSessionStore = (options) => createDrizzleSessionStore(options.db);
|
|
2973
|
+
var createVoiceDrizzleTaskStore = (options) => createDrizzleTaskStore(options.db);
|
|
2974
|
+
var createVoiceDrizzleTelephonyWebhookIdempotencyStore = (options) => createDrizzleTelephonyWebhookIdempotencyStore(options.db);
|
|
2975
|
+
var createVoiceDrizzleTraceEventStore = (options) => createDrizzleTraceEventStore(options.db);
|
|
2976
|
+
var createVoiceDrizzleTraceSinkDeliveryStore = (options) => createDrizzleTraceSinkDeliveryStore(options.db);
|
|
2977
|
+
|
|
2978
|
+
// src/drizzle/index.ts
|
|
2979
|
+
var voiceDrizzleSchema = {
|
|
2980
|
+
...voiceRuntimeStorageDrizzleSchema,
|
|
2981
|
+
voiceAssistantMemory: voiceAssistantMemoryTable,
|
|
2982
|
+
voiceEvalBaseline: voiceEvalBaselineTable,
|
|
2983
|
+
voiceHandoffDeliveries: voiceHandoffDeliveriesTable,
|
|
2984
|
+
voiceObservabilityExportReceipts: voiceObservabilityExportDeliveryReceiptsTable,
|
|
2985
|
+
voiceRealCallProfileEvidence: voiceRealCallProfileEvidenceTable,
|
|
2986
|
+
voiceRealCallProfileRecoveryJobs: voiceRealCallProfileRecoveryJobsTable
|
|
2987
|
+
};
|
|
2988
|
+
export {
|
|
2989
|
+
voiceTracesTable,
|
|
2990
|
+
voiceTraceDeliveriesTable,
|
|
2991
|
+
voiceTelephonyWebhookIdempotencyTable,
|
|
2992
|
+
voiceTasksTable,
|
|
2993
|
+
voiceSessionsTable,
|
|
2994
|
+
voiceRuntimeStorageDrizzleSchema,
|
|
2995
|
+
voiceReviewsTable,
|
|
2996
|
+
voiceRealCallProfileRecoveryJobsTable,
|
|
2997
|
+
voiceRealCallProfileEvidenceTable,
|
|
2998
|
+
voiceObservabilityExportDeliveryReceiptsTable,
|
|
2999
|
+
voiceHandoffDeliveriesTable,
|
|
3000
|
+
voiceExternalObjectsTable,
|
|
3001
|
+
voiceEventsTable,
|
|
3002
|
+
voiceEvalBaselineTable,
|
|
3003
|
+
voiceDrizzleSchema,
|
|
3004
|
+
voiceDocumentTable,
|
|
3005
|
+
voiceCampaignsTable,
|
|
3006
|
+
voiceAuditTable,
|
|
3007
|
+
voiceAuditDeliveriesTable,
|
|
3008
|
+
voiceAssistantMemoryTable,
|
|
3009
|
+
createVoiceDrizzleTraceSinkDeliveryStore,
|
|
3010
|
+
createVoiceDrizzleTraceEventStore,
|
|
3011
|
+
createVoiceDrizzleTelephonyWebhookIdempotencyStore,
|
|
3012
|
+
createVoiceDrizzleTaskStore,
|
|
3013
|
+
createVoiceDrizzleSessionStore,
|
|
3014
|
+
createVoiceDrizzleRuntimeStorage,
|
|
3015
|
+
createVoiceDrizzleReviewStore,
|
|
3016
|
+
createVoiceDrizzleRecordStore,
|
|
3017
|
+
createVoiceDrizzleRealCallProfileRecoveryJobStore,
|
|
3018
|
+
createVoiceDrizzleRealCallProfileEvidenceStore,
|
|
3019
|
+
createVoiceDrizzleObservabilityExportDeliveryReceiptStore,
|
|
3020
|
+
createVoiceDrizzleIntegrationEventStore,
|
|
3021
|
+
createVoiceDrizzleHandoffDeliveryStore,
|
|
3022
|
+
createVoiceDrizzleExternalObjectMapStore,
|
|
3023
|
+
createVoiceDrizzleEvalBaselineStore,
|
|
3024
|
+
createVoiceDrizzleCampaignStore,
|
|
3025
|
+
createVoiceDrizzleAuditSinkDeliveryStore,
|
|
3026
|
+
createVoiceDrizzleAuditEventStore,
|
|
3027
|
+
createVoiceDrizzleAssistantMemoryStore
|
|
3028
|
+
};
|