@absolutejs/voice 0.0.22-beta.367 → 0.0.22-beta.368
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/README.md +85 -0
- package/dist/client/index.js +157 -0
- package/dist/index.d.ts +2 -2
- package/dist/index.js +169 -10
- package/dist/proofTrends.d.ts +15 -0
- package/dist/react/index.js +157 -0
- package/dist/vue/index.js +157 -0
- package/package.json +1 -1
package/README.md
CHANGED
|
@@ -1401,6 +1401,91 @@ assertVoiceProductionReadinessEvidence(readiness, {
|
|
|
1401
1401
|
});
|
|
1402
1402
|
```
|
|
1403
1403
|
|
|
1404
|
+
Use `createVoiceProductionReadinessProofRuntime(...)` when the app needs a fresh, isolated proof window instead of letting stale local traces certify a deploy. The runtime is intentionally small: it owns a bounded in-memory trace store, route-cache defaults, a reusable TTL cache, a proof-freshness check, and optional synthetic provider/live-latency seed events. Your app still mounts routes, writes artifacts, and decides which proof sources matter.
|
|
1405
|
+
|
|
1406
|
+
```ts
|
|
1407
|
+
import {
|
|
1408
|
+
createVoiceProductionReadinessProofRuntime,
|
|
1409
|
+
createVoiceProductionReadinessRoutes,
|
|
1410
|
+
createVoiceReadinessProfile
|
|
1411
|
+
} from '@absolutejs/voice';
|
|
1412
|
+
|
|
1413
|
+
const readinessProof = createVoiceProductionReadinessProofRuntime({
|
|
1414
|
+
cacheMs: 10_000,
|
|
1415
|
+
traceMaxAgeMs: 30 * 60_000
|
|
1416
|
+
});
|
|
1417
|
+
|
|
1418
|
+
const refreshReadinessProof = () =>
|
|
1419
|
+
readinessProof.refresh(async (metadata) => {
|
|
1420
|
+
await readinessProof.seedTraceProof({
|
|
1421
|
+
llmProvider: 'openai',
|
|
1422
|
+
scenarioId: 'provider-slo-proof',
|
|
1423
|
+
sttProvider: 'deepgram',
|
|
1424
|
+
ttsProvider: 'openai'
|
|
1425
|
+
});
|
|
1426
|
+
|
|
1427
|
+
await writeProofPack({
|
|
1428
|
+
generatedAt: metadata.generatedAt,
|
|
1429
|
+
runId: metadata.runId
|
|
1430
|
+
});
|
|
1431
|
+
});
|
|
1432
|
+
|
|
1433
|
+
app.use(
|
|
1434
|
+
createVoiceProductionReadinessRoutes({
|
|
1435
|
+
...createVoiceReadinessProfile('phone-agent', {
|
|
1436
|
+
explain: true
|
|
1437
|
+
}),
|
|
1438
|
+
additionalChecks: async () => [
|
|
1439
|
+
await readinessProof.buildFreshnessCheck()
|
|
1440
|
+
],
|
|
1441
|
+
cacheMs: readinessProof.options.cacheMs,
|
|
1442
|
+
providerSlo: async () => {
|
|
1443
|
+
await refreshReadinessProof();
|
|
1444
|
+
return {
|
|
1445
|
+
events: await readinessProof.store.list(),
|
|
1446
|
+
requiredKinds: ['llm', 'stt', 'tts']
|
|
1447
|
+
};
|
|
1448
|
+
},
|
|
1449
|
+
resolveOptions: async () => {
|
|
1450
|
+
await refreshReadinessProof();
|
|
1451
|
+
return {};
|
|
1452
|
+
},
|
|
1453
|
+
store: readinessProof.store,
|
|
1454
|
+
traceMaxAgeMs: readinessProof.options.traceMaxAgeMs
|
|
1455
|
+
})
|
|
1456
|
+
);
|
|
1457
|
+
```
|
|
1458
|
+
|
|
1459
|
+
This primitive does not start workers, create persistent storage, mount a dashboard, or prescribe a deploy workflow. It only gives self-hosted apps one clean readiness-proof runtime so JSON, HTML, gate checks, proof packs, and trend artifacts agree on the same fresh evidence window.
|
|
1460
|
+
|
|
1461
|
+
Use `buildVoiceRealCallProfileEvidenceFromTraceEvents(...)` or `loadVoiceRealCallProfileEvidenceFromTraceStore(...)` when repeated real browser/phone sessions should drive profile defaults and provider/runtime recommendations. These helpers read ordinary trace events such as `session.error`, `provider.decision`, `client.live_latency`, `client.browser_media`, `client.telephony_media`, `client.barge_in`, and `turn_latency.stage`, then emit `VoiceProofTrendRealCallProfileEvidence[]` for `buildVoiceRealCallProfileHistoryReport(...)`.
|
|
1462
|
+
|
|
1463
|
+
```ts
|
|
1464
|
+
import {
|
|
1465
|
+
buildVoiceRealCallProfileHistoryReport,
|
|
1466
|
+
createVoiceRealCallProfileHistoryRoutes,
|
|
1467
|
+
loadVoiceRealCallProfileEvidenceFromTraceStore
|
|
1468
|
+
} from '@absolutejs/voice';
|
|
1469
|
+
|
|
1470
|
+
const buildRealCallHistory = async () =>
|
|
1471
|
+
buildVoiceRealCallProfileHistoryReport({
|
|
1472
|
+
evidence: await loadVoiceRealCallProfileEvidenceFromTraceStore({
|
|
1473
|
+
defaultProfileId: 'meeting-recorder',
|
|
1474
|
+
defaultProfileLabel: 'Meeting recorder',
|
|
1475
|
+
store: runtime.traces
|
|
1476
|
+
}),
|
|
1477
|
+
source: 'runtime.traces'
|
|
1478
|
+
});
|
|
1479
|
+
|
|
1480
|
+
app.use(
|
|
1481
|
+
createVoiceRealCallProfileHistoryRoutes({
|
|
1482
|
+
source: buildRealCallHistory
|
|
1483
|
+
})
|
|
1484
|
+
);
|
|
1485
|
+
```
|
|
1486
|
+
|
|
1487
|
+
The point is not to benchmark a fake demo once. The point is to let every real call add profile evidence so `/api/voice/real-call-profile-history`, provider recommendations, profile-switch readiness, and operations records can explain which provider/runtime path is winning for each call shape.
|
|
1488
|
+
|
|
1404
1489
|
Built-in profiles:
|
|
1405
1490
|
|
|
1406
1491
|
- `meeting-recorder`: live latency, session health, provider fallback, routing contracts, reconnect proof, and barge-in interruption proof.
|
package/dist/client/index.js
CHANGED
|
@@ -4016,6 +4016,20 @@ var maxNumber = (values) => {
|
|
|
4016
4016
|
const finite = values.filter((value) => typeof value === "number" && Number.isFinite(value));
|
|
4017
4017
|
return finite.length > 0 ? Math.max(...finite) : undefined;
|
|
4018
4018
|
};
|
|
4019
|
+
var percentile = (values, rank) => {
|
|
4020
|
+
const finite = values.filter((value) => Number.isFinite(value)).sort((left, right) => left - right);
|
|
4021
|
+
if (finite.length === 0) {
|
|
4022
|
+
return;
|
|
4023
|
+
}
|
|
4024
|
+
const index = Math.min(finite.length - 1, Math.max(0, Math.ceil(rank / 100 * finite.length) - 1));
|
|
4025
|
+
return finite[index];
|
|
4026
|
+
};
|
|
4027
|
+
var averageNumber = (values) => {
|
|
4028
|
+
const finite = values.filter((value) => Number.isFinite(value));
|
|
4029
|
+
return finite.length === 0 ? undefined : Math.round(finite.reduce((total, value) => total + value, 0) / finite.length);
|
|
4030
|
+
};
|
|
4031
|
+
var readString = (value) => typeof value === "string" && value.trim() ? value : undefined;
|
|
4032
|
+
var readNumber2 = (value) => typeof value === "number" && Number.isFinite(value) ? value : undefined;
|
|
4019
4033
|
var readProofTrendMaxLiveP95 = (report) => report.summary.maxLiveP95Ms ?? maxNumber(report.cycles.map((cycle) => cycle.liveLatency?.p95Ms));
|
|
4020
4034
|
var readProofTrendMaxProviderP95 = (report) => report.summary.maxProviderP95Ms ?? maxNumber((report.summary.providers ?? []).map((provider) => provider.p95Ms)) ?? maxNumber(report.cycles.flatMap((cycle) => (cycle.providers ?? []).map((provider) => provider.p95Ms)));
|
|
4021
4035
|
var readProofTrendMaxTurnP95 = (report) => report.summary.maxTurnP95Ms ?? maxNumber(report.cycles.map((cycle) => cycle.turnLatency?.p95Ms));
|
|
@@ -4079,6 +4093,149 @@ var aggregateProofTrendRuntimeChannel = (channels) => {
|
|
|
4079
4093
|
status: channels.some((channel) => channel.status === "fail") ? "fail" : channels.some((channel) => channel.status === "warn") ? "warn" : channels.every((channel) => channel.status === "pass") ? "pass" : undefined
|
|
4080
4094
|
};
|
|
4081
4095
|
};
|
|
4096
|
+
var readTraceRecord = (event) => event.payload;
|
|
4097
|
+
var readTraceProfileId = (events, options) => {
|
|
4098
|
+
for (const event of events) {
|
|
4099
|
+
const payload = readTraceRecord(event);
|
|
4100
|
+
const profileId = readString(payload.profileId) ?? readString(event.metadata?.profileId) ?? readString(payload.benchmarkProfileId) ?? readString(event.metadata?.benchmarkProfileId);
|
|
4101
|
+
if (profileId) {
|
|
4102
|
+
return profileId;
|
|
4103
|
+
}
|
|
4104
|
+
}
|
|
4105
|
+
return options.defaultProfileId;
|
|
4106
|
+
};
|
|
4107
|
+
var readProviderTraceRole = (payload) => readString(payload.kind) ?? readString(payload.role) ?? readString(payload.surface) ?? "provider";
|
|
4108
|
+
var readProviderTraceLatency = (payload) => readNumber2(payload.elapsedMs) ?? readNumber2(payload.latencyMs) ?? readNumber2(payload.durationMs);
|
|
4109
|
+
var readProviderTraceId = (payload) => readString(payload.selectedProvider) ?? readString(payload.provider) ?? readString(payload.model) ?? readString(payload.adapter);
|
|
4110
|
+
var readTraceStatus = (payload) => readString(payload.providerStatus) ?? readString(payload.status);
|
|
4111
|
+
var isFailingTraceStatus = (status) => status === "error" || status === "fail" || status === "failed" || status === "timeout";
|
|
4112
|
+
var summarizeProviderTraceEvidence = (events, maxProviderP95Ms) => {
|
|
4113
|
+
const providerLatencies = new Map;
|
|
4114
|
+
const providerMeta = new Map;
|
|
4115
|
+
for (const event of events) {
|
|
4116
|
+
if (event.type !== "session.error" && event.type !== "provider.decision") {
|
|
4117
|
+
continue;
|
|
4118
|
+
}
|
|
4119
|
+
const payload = readTraceRecord(event);
|
|
4120
|
+
const provider = readProviderTraceId(payload);
|
|
4121
|
+
if (!provider) {
|
|
4122
|
+
continue;
|
|
4123
|
+
}
|
|
4124
|
+
const role = readProviderTraceRole(payload);
|
|
4125
|
+
const id = `${role}:${provider}`;
|
|
4126
|
+
const latency = readProviderTraceLatency(payload);
|
|
4127
|
+
if (latency !== undefined) {
|
|
4128
|
+
providerLatencies.set(id, [...providerLatencies.get(id) ?? [], latency]);
|
|
4129
|
+
}
|
|
4130
|
+
const existing = providerMeta.get(id);
|
|
4131
|
+
providerMeta.set(id, {
|
|
4132
|
+
failed: existing?.failed === true || isFailingTraceStatus(readTraceStatus(payload)),
|
|
4133
|
+
label: existing?.label ?? `${role.toUpperCase()} ${provider}`,
|
|
4134
|
+
role: existing?.role ?? role
|
|
4135
|
+
});
|
|
4136
|
+
}
|
|
4137
|
+
return [...providerMeta.entries()].map(([id, meta]) => {
|
|
4138
|
+
const latencies = providerLatencies.get(id) ?? [];
|
|
4139
|
+
const p95Ms = percentile(latencies, 95);
|
|
4140
|
+
return {
|
|
4141
|
+
averageMs: averageNumber(latencies),
|
|
4142
|
+
id,
|
|
4143
|
+
label: meta.label,
|
|
4144
|
+
p50Ms: percentile(latencies, 50),
|
|
4145
|
+
p95Ms,
|
|
4146
|
+
role: meta.role,
|
|
4147
|
+
samples: latencies.length,
|
|
4148
|
+
status: meta.failed || (p95Ms ?? 0) > (maxProviderP95Ms ?? Number.POSITIVE_INFINITY) ? "fail" : latencies.length > 0 ? "pass" : "warn"
|
|
4149
|
+
};
|
|
4150
|
+
});
|
|
4151
|
+
};
|
|
4152
|
+
var summarizeTurnTraceP95 = (events) => {
|
|
4153
|
+
const explicit = events.filter((event) => event.type === "turn_latency.stage").map((event) => {
|
|
4154
|
+
const payload = readTraceRecord(event);
|
|
4155
|
+
return readNumber2(payload.totalMs) ?? readNumber2(payload.elapsedMs) ?? readNumber2(payload.latencyMs);
|
|
4156
|
+
}).filter((value) => value !== undefined);
|
|
4157
|
+
if (explicit.length > 0) {
|
|
4158
|
+
return percentile(explicit, 95);
|
|
4159
|
+
}
|
|
4160
|
+
const turnStages = new Map;
|
|
4161
|
+
for (const event of events) {
|
|
4162
|
+
if (event.type !== "turn_latency.stage" || !event.turnId) {
|
|
4163
|
+
continue;
|
|
4164
|
+
}
|
|
4165
|
+
const key = `${event.sessionId}:${event.turnId}`;
|
|
4166
|
+
turnStages.set(key, [...turnStages.get(key) ?? [], event.at]);
|
|
4167
|
+
}
|
|
4168
|
+
const totals = [...turnStages.values()].map((stages) => stages.length < 2 ? undefined : Math.max(...stages) - Math.min(...stages)).filter((value) => value !== undefined);
|
|
4169
|
+
return percentile(totals, 95);
|
|
4170
|
+
};
|
|
4171
|
+
var summarizeRuntimeChannelTraceEvidence = (events) => {
|
|
4172
|
+
const runtimeEvents = events.filter((event) => event.type === "client.browser_media" || event.type === "client.telephony_media" || event.type === "client.barge_in");
|
|
4173
|
+
if (runtimeEvents.length === 0) {
|
|
4174
|
+
return;
|
|
4175
|
+
}
|
|
4176
|
+
const firstAudio = runtimeEvents.map((event) => readNumber2(readTraceRecord(event).firstAudioLatencyMs)).filter((value) => value !== undefined);
|
|
4177
|
+
const jitter = runtimeEvents.map((event) => readNumber2(readTraceRecord(event).jitterMs)).filter((value) => value !== undefined);
|
|
4178
|
+
const timestampDrift = runtimeEvents.map((event) => readNumber2(readTraceRecord(event).timestampDriftMs)).filter((value) => value !== undefined);
|
|
4179
|
+
const backpressure = runtimeEvents.map((event) => readNumber2(readTraceRecord(event).backpressureEvents)).filter((value) => value !== undefined);
|
|
4180
|
+
const interruptions = runtimeEvents.map((event) => {
|
|
4181
|
+
const payload = readTraceRecord(event);
|
|
4182
|
+
return readNumber2(payload.interruptionLatencyMs) ?? readNumber2(payload.interruptionMs) ?? readNumber2(payload.elapsedMs);
|
|
4183
|
+
}).filter((value) => value !== undefined);
|
|
4184
|
+
return {
|
|
4185
|
+
maxBackpressureEvents: maxNumber(backpressure),
|
|
4186
|
+
maxFirstAudioLatencyMs: maxNumber(firstAudio),
|
|
4187
|
+
maxInterruptionP95Ms: percentile(interruptions, 95),
|
|
4188
|
+
maxJitterMs: maxNumber(jitter),
|
|
4189
|
+
maxTimestampDriftMs: maxNumber(timestampDrift),
|
|
4190
|
+
samples: runtimeEvents.length,
|
|
4191
|
+
status: runtimeEvents.some((event) => isFailingTraceStatus(readTraceStatus(readTraceRecord(event)))) ? "fail" : "pass"
|
|
4192
|
+
};
|
|
4193
|
+
};
|
|
4194
|
+
var buildVoiceRealCallProfileEvidenceFromTraceEvents = (events, options = {}) => {
|
|
4195
|
+
const sessionFilter = new Set(options.sessionIds ?? []);
|
|
4196
|
+
const eventsBySession = new Map;
|
|
4197
|
+
for (const event of events) {
|
|
4198
|
+
if (sessionFilter.size > 0 && !sessionFilter.has(event.sessionId)) {
|
|
4199
|
+
continue;
|
|
4200
|
+
}
|
|
4201
|
+
eventsBySession.set(event.sessionId, [
|
|
4202
|
+
...eventsBySession.get(event.sessionId) ?? [],
|
|
4203
|
+
event
|
|
4204
|
+
]);
|
|
4205
|
+
}
|
|
4206
|
+
return [...eventsBySession.entries()].map(([
|
|
4207
|
+
sessionId,
|
|
4208
|
+
sessionEvents
|
|
4209
|
+
]) => {
|
|
4210
|
+
const profileId = readTraceProfileId(sessionEvents, options);
|
|
4211
|
+
if (!profileId) {
|
|
4212
|
+
return;
|
|
4213
|
+
}
|
|
4214
|
+
const providers = summarizeProviderTraceEvidence(sessionEvents, options.maxProviderP95Ms);
|
|
4215
|
+
const liveLatencies = sessionEvents.filter((event) => event.type === "client.live_latency").map((event) => {
|
|
4216
|
+
const payload = readTraceRecord(event);
|
|
4217
|
+
return readNumber2(payload.latencyMs) ?? readNumber2(payload.elapsedMs);
|
|
4218
|
+
}).filter((value) => value !== undefined);
|
|
4219
|
+
const turnP95Ms = summarizeTurnTraceP95(sessionEvents);
|
|
4220
|
+
const providerP95Ms = maxNumber(providers.map((provider) => provider.p95Ms));
|
|
4221
|
+
const runtimeChannel = summarizeRuntimeChannelTraceEvidence(sessionEvents);
|
|
4222
|
+
return {
|
|
4223
|
+
generatedAt: new Date(Math.max(...sessionEvents.map((event) => event.at))).toISOString(),
|
|
4224
|
+
liveP95Ms: percentile(liveLatencies, 95),
|
|
4225
|
+
ok: providers.every((provider) => provider.status !== "fail") && (runtimeChannel?.status ?? "pass") !== "fail",
|
|
4226
|
+
operationsRecordHref: `/voice-operations/${sessionId}`,
|
|
4227
|
+
profileDescription: options.profileDescriptions?.[profileId],
|
|
4228
|
+
profileId,
|
|
4229
|
+
profileLabel: options.profileLabels?.[profileId] ?? (profileId === options.defaultProfileId ? options.defaultProfileLabel : undefined),
|
|
4230
|
+
providerP95Ms,
|
|
4231
|
+
providers,
|
|
4232
|
+
runtimeChannel,
|
|
4233
|
+
sessionId,
|
|
4234
|
+
turnP95Ms
|
|
4235
|
+
};
|
|
4236
|
+
}).filter((evidence) => evidence !== undefined);
|
|
4237
|
+
};
|
|
4238
|
+
var loadVoiceRealCallProfileEvidenceFromTraceStore = async (options) => buildVoiceRealCallProfileEvidenceFromTraceEvents(await options.store.list({ limit: options.limit ?? 5000 }), options);
|
|
4082
4239
|
var readProofTrendProviders = (reports) => aggregateProofTrendProviders(reports.flatMap((report) => report.summary.providers && report.summary.providers.length > 0 ? report.summary.providers : report.cycles.flatMap((cycle) => cycle.providers ?? [])));
|
|
4083
4240
|
var exceedsProofTrendBudget = (value, budget) => value !== undefined && (!Number.isFinite(value) || value > budget);
|
|
4084
4241
|
var readProofTrendProfileStatus = (profile, budgets) => {
|
package/dist/index.d.ts
CHANGED
|
@@ -30,12 +30,12 @@ export { assertVoicePlatformCoverage, buildVoicePlatformCoverageSummary, createV
|
|
|
30
30
|
export { assertVoiceCompetitiveCoverage, buildVoiceCompetitiveCoverageReport, createVoiceCompetitiveCoverageRoutes, evaluateVoiceCompetitiveCoverage, renderVoiceCompetitiveCoverageHTML, renderVoiceCompetitiveCoverageMarkdown } from './competitiveCoverage';
|
|
31
31
|
export type { VoiceCompetitiveCoverageAssertionInput, VoiceCompetitiveCoverageAssertionReport, VoiceCompetitiveCoverageIssue, VoiceCompetitiveCoverageLevel, VoiceCompetitiveCoverageReport, VoiceCompetitiveCoverageReportInput, VoiceCompetitiveCoverageRoutesOptions, VoiceCompetitiveCoverageStatus, VoiceCompetitiveCoverageSummary, VoiceCompetitiveDepthLevel, VoiceCompetitiveEvidence, VoiceCompetitiveSurface } from './competitiveCoverage';
|
|
32
32
|
export type { VoicePlatformCoverageAssertionInput, VoicePlatformCoverageAssertionReport, VoicePlatformCoverageEvidence, VoicePlatformCoverageRoutesOptions, VoicePlatformCoverageStatus, VoicePlatformCoverageSummary, VoicePlatformCoverageSummaryInput, VoicePlatformCoverageSurface } from './platformCoverage';
|
|
33
|
-
export { assertVoiceProofTrendEvidence, buildEmptyVoiceProofTrendReport, buildVoiceProofTrendProfileSummaries, buildVoiceProofTrendRecommendationReport, buildVoiceProofTrendReportFromRealCallProfiles, buildVoiceProofTrendReport, buildVoiceRealCallProfileDefaults, buildVoiceRealCallProfileHistoryReport, createVoiceProofTrendRecommendationRoutes, createVoiceProofTrendRoutes, createVoiceRealCallProfileHistoryRoutes, DEFAULT_VOICE_PROOF_TREND_PROFILE_DEFINITIONS, DEFAULT_VOICE_PROOF_TRENDS_MAX_AGE_MS, evaluateVoiceProofTrendEvidence, formatVoiceProofTrendAge, normalizeVoiceProofTrendReport, readVoiceProofTrendReportFile, renderVoiceProofTrendRecommendationHTML, renderVoiceProofTrendRecommendationMarkdown, renderVoiceRealCallProfileHistoryHTML, renderVoiceRealCallProfileHistoryMarkdown, resolveVoiceRealCallProfileProviderRoute } from './proofTrends';
|
|
33
|
+
export { assertVoiceProofTrendEvidence, buildEmptyVoiceProofTrendReport, buildVoiceProofTrendProfileSummaries, buildVoiceProofTrendRecommendationReport, buildVoiceProofTrendReportFromRealCallProfiles, buildVoiceProofTrendReport, buildVoiceRealCallProfileEvidenceFromTraceEvents, buildVoiceRealCallProfileDefaults, buildVoiceRealCallProfileHistoryReport, createVoiceProofTrendRecommendationRoutes, createVoiceProofTrendRoutes, createVoiceRealCallProfileHistoryRoutes, DEFAULT_VOICE_PROOF_TREND_PROFILE_DEFINITIONS, DEFAULT_VOICE_PROOF_TRENDS_MAX_AGE_MS, evaluateVoiceProofTrendEvidence, formatVoiceProofTrendAge, loadVoiceRealCallProfileEvidenceFromTraceStore, normalizeVoiceProofTrendReport, readVoiceProofTrendReportFile, renderVoiceProofTrendRecommendationHTML, renderVoiceProofTrendRecommendationMarkdown, renderVoiceRealCallProfileHistoryHTML, renderVoiceRealCallProfileHistoryMarkdown, resolveVoiceRealCallProfileProviderRoute } from './proofTrends';
|
|
34
34
|
export { applyVoiceProfileSwitchGuard, buildVoiceProfileSwitchReadinessReport, buildVoiceProfileSwitchLiveDecisionReport, createVoiceProfileSwitchLiveDecisionRoutes, createVoiceProfileSwitchPolicyProofRoutes, createVoiceProfileSwitchReadinessRoutes, recommendVoiceProfileSwitch, renderVoiceProfileSwitchLiveDecisionHTML, renderVoiceProfileSwitchPolicyProofHTML, renderVoiceProfileSwitchReadinessHTML, runVoiceProfileSwitchPolicyProof } from './profileSwitchRecommendation';
|
|
35
35
|
export type { VoiceProfileSwitchGuardAction, VoiceProfileSwitchGuardDecision, VoiceProfileSwitchGuardMode, VoiceProfileSwitchGuardOptions, VoiceProfileSwitchObservedSignals, VoiceProfileSwitchLiveDecisionEvidence, VoiceProfileSwitchLiveDecisionReport, VoiceProfileSwitchLiveDecisionReportOptions, VoiceProfileSwitchLiveDecisionRoutesOptions, VoiceProfileSwitchLiveDecisionSession, VoiceProfileSwitchPolicyProofCase, VoiceProfileSwitchPolicyProofCaseResult, VoiceProfileSwitchPolicyProofOptions, VoiceProfileSwitchPolicyProofReport, VoiceProfileSwitchPolicyProofRoutesOptions, VoiceProfileSwitchReadinessIssue, VoiceProfileSwitchReadinessOptions, VoiceProfileSwitchReadinessReport, VoiceProfileSwitchReadinessRoutesOptions, VoiceProfileSwitchReadinessStatus, VoiceProfileSwitchRecommendation, VoiceProfileSwitchRecommendationOptions } from './profileSwitchRecommendation';
|
|
36
36
|
export { buildVoiceProviderDecisionTraceReport, createVoiceProviderDecisionTraceEvent, createVoiceProviderDecisionTraceRoutes, listVoiceProviderDecisionTraces, renderVoiceProviderDecisionTraceHTML, renderVoiceProviderDecisionTraceMarkdown } from './providerDecisionTraces';
|
|
37
37
|
export type { VoiceProviderDecisionStatus, VoiceProviderDecisionSurfaceReport, VoiceProviderDecisionTrace, VoiceProviderDecisionTraceInput, VoiceProviderDecisionTraceIssue, VoiceProviderDecisionTraceReport, VoiceProviderDecisionTraceReportOptions, VoiceProviderDecisionTraceRoutesOptions } from './providerDecisionTraces';
|
|
38
|
-
export type { VoiceProofTrendAssertionInput, VoiceProofTrendAssertionReport, VoiceProofTrendCycle, VoiceProofTrendProfileDefinition, VoiceProofTrendProfileRecommendation, VoiceProofTrendProfileSummaryOptions, VoiceProofTrendProfileSummary, VoiceProofTrendProviderRecommendation, VoiceProofTrendProviderSummary, VoiceProofTrendRecommendation, VoiceProofTrendRecommendationOptions, VoiceProofTrendRecommendationReport, VoiceProofTrendRecommendationRoutesOptions, VoiceProofTrendRecommendationStatus, VoiceProofTrendRecommendationSurface, VoiceProofTrendRealCallProfileEvidence, VoiceProofTrendRealCallProfileReportOptions, VoiceProofTrendReport, VoiceProofTrendReportInput, VoiceProofTrendRoutesOptions, VoiceProofTrendRuntimeChannelSummary, VoiceProofTrendStatus, VoiceProofTrendSummary, VoiceRealCallProfileDefault, VoiceRealCallProfileDefaultsOptions, VoiceRealCallProfileDefaultsReport, VoiceRealCallProfileHistoryOptions, VoiceRealCallProfileHistoryReport, VoiceRealCallProfileHistoryRoutesOptions, VoiceRealCallProfileProviderRouteOptions } from './proofTrends';
|
|
38
|
+
export type { VoiceProofTrendAssertionInput, VoiceProofTrendAssertionReport, VoiceProofTrendCycle, VoiceProofTrendProfileDefinition, VoiceProofTrendProfileRecommendation, VoiceProofTrendProfileSummaryOptions, VoiceProofTrendProfileSummary, VoiceProofTrendProviderRecommendation, VoiceProofTrendProviderSummary, VoiceProofTrendRecommendation, VoiceProofTrendRecommendationOptions, VoiceProofTrendRecommendationReport, VoiceProofTrendRecommendationRoutesOptions, VoiceProofTrendRecommendationStatus, VoiceProofTrendRecommendationSurface, VoiceProofTrendRealCallProfileEvidence, VoiceProofTrendRealCallProfileReportOptions, VoiceProofTrendReport, VoiceProofTrendReportInput, VoiceProofTrendRoutesOptions, VoiceProofTrendRuntimeChannelSummary, VoiceProofTrendStatus, VoiceProofTrendSummary, VoiceRealCallProfileDefault, VoiceRealCallProfileDefaultsOptions, VoiceRealCallProfileDefaultsReport, VoiceRealCallProfileHistoryOptions, VoiceRealCallProfileHistoryReport, VoiceRealCallProfileHistoryRoutesOptions, VoiceRealCallProfileProviderRouteOptions, VoiceRealCallProfileTraceEvidenceOptions, VoiceRealCallProfileTraceStoreEvidenceOptions } from './proofTrends';
|
|
39
39
|
export { assertVoiceSloCalibration, buildVoiceSloCalibrationReport, buildVoiceSloReadinessThresholdReport, createVoiceSloReadinessThresholdOptions, createVoiceSloReadinessThresholdRoutes, createVoiceSloThresholdProfile, createVoiceSloCalibrationRoutes, renderVoiceSloCalibrationMarkdown, renderVoiceSloReadinessThresholdHTML, renderVoiceSloReadinessThresholdMarkdown } from './sloCalibration';
|
|
40
40
|
export type { VoiceSloCalibrationMetricKey, VoiceSloCalibrationOptions, VoiceSloCalibrationReport, VoiceSloCalibrationRoutesOptions, VoiceSloCalibrationSample, VoiceSloCalibrationStatus, VoiceSloCalibrationThreshold, VoiceSloCalibrationThresholds, VoiceSloReadinessThresholdReport, VoiceSloReadinessThresholdReportOptions, VoiceSloReadinessThresholdOptions, VoiceSloReadinessThresholdRoutesOptions, VoiceSloThresholdProfile } from './sloCalibration';
|
|
41
41
|
export { assertVoiceLiveOpsControlEvidence, assertVoiceLiveOpsEvidence, buildVoiceLiveOpsControlState, createVoiceLiveOpsController, createVoiceLiveOpsRoutes, createVoiceMemoryLiveOpsControlStore, evaluateVoiceLiveOpsControlEvidence, evaluateVoiceLiveOpsEvidence, getVoiceLiveOpsControlStatus, VOICE_LIVE_OPS_ACTIONS } from './liveOps';
|
package/dist/index.js
CHANGED
|
@@ -15445,6 +15445,20 @@ var maxNumber = (values) => {
|
|
|
15445
15445
|
const finite = values.filter((value) => typeof value === "number" && Number.isFinite(value));
|
|
15446
15446
|
return finite.length > 0 ? Math.max(...finite) : undefined;
|
|
15447
15447
|
};
|
|
15448
|
+
var percentile2 = (values, rank) => {
|
|
15449
|
+
const finite = values.filter((value) => Number.isFinite(value)).sort((left, right) => left - right);
|
|
15450
|
+
if (finite.length === 0) {
|
|
15451
|
+
return;
|
|
15452
|
+
}
|
|
15453
|
+
const index = Math.min(finite.length - 1, Math.max(0, Math.ceil(rank / 100 * finite.length) - 1));
|
|
15454
|
+
return finite[index];
|
|
15455
|
+
};
|
|
15456
|
+
var averageNumber = (values) => {
|
|
15457
|
+
const finite = values.filter((value) => Number.isFinite(value));
|
|
15458
|
+
return finite.length === 0 ? undefined : Math.round(finite.reduce((total, value) => total + value, 0) / finite.length);
|
|
15459
|
+
};
|
|
15460
|
+
var readString2 = (value) => typeof value === "string" && value.trim() ? value : undefined;
|
|
15461
|
+
var readNumber2 = (value) => typeof value === "number" && Number.isFinite(value) ? value : undefined;
|
|
15448
15462
|
var readProofTrendMaxLiveP95 = (report) => report.summary.maxLiveP95Ms ?? maxNumber(report.cycles.map((cycle) => cycle.liveLatency?.p95Ms));
|
|
15449
15463
|
var readProofTrendMaxProviderP95 = (report) => report.summary.maxProviderP95Ms ?? maxNumber((report.summary.providers ?? []).map((provider) => provider.p95Ms)) ?? maxNumber(report.cycles.flatMap((cycle) => (cycle.providers ?? []).map((provider) => provider.p95Ms)));
|
|
15450
15464
|
var readProofTrendMaxTurnP95 = (report) => report.summary.maxTurnP95Ms ?? maxNumber(report.cycles.map((cycle) => cycle.turnLatency?.p95Ms));
|
|
@@ -15508,6 +15522,149 @@ var aggregateProofTrendRuntimeChannel = (channels) => {
|
|
|
15508
15522
|
status: channels.some((channel) => channel.status === "fail") ? "fail" : channels.some((channel) => channel.status === "warn") ? "warn" : channels.every((channel) => channel.status === "pass") ? "pass" : undefined
|
|
15509
15523
|
};
|
|
15510
15524
|
};
|
|
15525
|
+
var readTraceRecord = (event) => event.payload;
|
|
15526
|
+
var readTraceProfileId = (events, options) => {
|
|
15527
|
+
for (const event of events) {
|
|
15528
|
+
const payload = readTraceRecord(event);
|
|
15529
|
+
const profileId = readString2(payload.profileId) ?? readString2(event.metadata?.profileId) ?? readString2(payload.benchmarkProfileId) ?? readString2(event.metadata?.benchmarkProfileId);
|
|
15530
|
+
if (profileId) {
|
|
15531
|
+
return profileId;
|
|
15532
|
+
}
|
|
15533
|
+
}
|
|
15534
|
+
return options.defaultProfileId;
|
|
15535
|
+
};
|
|
15536
|
+
var readProviderTraceRole = (payload) => readString2(payload.kind) ?? readString2(payload.role) ?? readString2(payload.surface) ?? "provider";
|
|
15537
|
+
var readProviderTraceLatency = (payload) => readNumber2(payload.elapsedMs) ?? readNumber2(payload.latencyMs) ?? readNumber2(payload.durationMs);
|
|
15538
|
+
var readProviderTraceId = (payload) => readString2(payload.selectedProvider) ?? readString2(payload.provider) ?? readString2(payload.model) ?? readString2(payload.adapter);
|
|
15539
|
+
var readTraceStatus = (payload) => readString2(payload.providerStatus) ?? readString2(payload.status);
|
|
15540
|
+
var isFailingTraceStatus = (status) => status === "error" || status === "fail" || status === "failed" || status === "timeout";
|
|
15541
|
+
var summarizeProviderTraceEvidence = (events, maxProviderP95Ms) => {
|
|
15542
|
+
const providerLatencies = new Map;
|
|
15543
|
+
const providerMeta = new Map;
|
|
15544
|
+
for (const event of events) {
|
|
15545
|
+
if (event.type !== "session.error" && event.type !== "provider.decision") {
|
|
15546
|
+
continue;
|
|
15547
|
+
}
|
|
15548
|
+
const payload = readTraceRecord(event);
|
|
15549
|
+
const provider = readProviderTraceId(payload);
|
|
15550
|
+
if (!provider) {
|
|
15551
|
+
continue;
|
|
15552
|
+
}
|
|
15553
|
+
const role = readProviderTraceRole(payload);
|
|
15554
|
+
const id = `${role}:${provider}`;
|
|
15555
|
+
const latency = readProviderTraceLatency(payload);
|
|
15556
|
+
if (latency !== undefined) {
|
|
15557
|
+
providerLatencies.set(id, [...providerLatencies.get(id) ?? [], latency]);
|
|
15558
|
+
}
|
|
15559
|
+
const existing = providerMeta.get(id);
|
|
15560
|
+
providerMeta.set(id, {
|
|
15561
|
+
failed: existing?.failed === true || isFailingTraceStatus(readTraceStatus(payload)),
|
|
15562
|
+
label: existing?.label ?? `${role.toUpperCase()} ${provider}`,
|
|
15563
|
+
role: existing?.role ?? role
|
|
15564
|
+
});
|
|
15565
|
+
}
|
|
15566
|
+
return [...providerMeta.entries()].map(([id, meta]) => {
|
|
15567
|
+
const latencies = providerLatencies.get(id) ?? [];
|
|
15568
|
+
const p95Ms = percentile2(latencies, 95);
|
|
15569
|
+
return {
|
|
15570
|
+
averageMs: averageNumber(latencies),
|
|
15571
|
+
id,
|
|
15572
|
+
label: meta.label,
|
|
15573
|
+
p50Ms: percentile2(latencies, 50),
|
|
15574
|
+
p95Ms,
|
|
15575
|
+
role: meta.role,
|
|
15576
|
+
samples: latencies.length,
|
|
15577
|
+
status: meta.failed || (p95Ms ?? 0) > (maxProviderP95Ms ?? Number.POSITIVE_INFINITY) ? "fail" : latencies.length > 0 ? "pass" : "warn"
|
|
15578
|
+
};
|
|
15579
|
+
});
|
|
15580
|
+
};
|
|
15581
|
+
var summarizeTurnTraceP95 = (events) => {
|
|
15582
|
+
const explicit = events.filter((event) => event.type === "turn_latency.stage").map((event) => {
|
|
15583
|
+
const payload = readTraceRecord(event);
|
|
15584
|
+
return readNumber2(payload.totalMs) ?? readNumber2(payload.elapsedMs) ?? readNumber2(payload.latencyMs);
|
|
15585
|
+
}).filter((value) => value !== undefined);
|
|
15586
|
+
if (explicit.length > 0) {
|
|
15587
|
+
return percentile2(explicit, 95);
|
|
15588
|
+
}
|
|
15589
|
+
const turnStages = new Map;
|
|
15590
|
+
for (const event of events) {
|
|
15591
|
+
if (event.type !== "turn_latency.stage" || !event.turnId) {
|
|
15592
|
+
continue;
|
|
15593
|
+
}
|
|
15594
|
+
const key = `${event.sessionId}:${event.turnId}`;
|
|
15595
|
+
turnStages.set(key, [...turnStages.get(key) ?? [], event.at]);
|
|
15596
|
+
}
|
|
15597
|
+
const totals = [...turnStages.values()].map((stages) => stages.length < 2 ? undefined : Math.max(...stages) - Math.min(...stages)).filter((value) => value !== undefined);
|
|
15598
|
+
return percentile2(totals, 95);
|
|
15599
|
+
};
|
|
15600
|
+
var summarizeRuntimeChannelTraceEvidence = (events) => {
|
|
15601
|
+
const runtimeEvents = events.filter((event) => event.type === "client.browser_media" || event.type === "client.telephony_media" || event.type === "client.barge_in");
|
|
15602
|
+
if (runtimeEvents.length === 0) {
|
|
15603
|
+
return;
|
|
15604
|
+
}
|
|
15605
|
+
const firstAudio = runtimeEvents.map((event) => readNumber2(readTraceRecord(event).firstAudioLatencyMs)).filter((value) => value !== undefined);
|
|
15606
|
+
const jitter = runtimeEvents.map((event) => readNumber2(readTraceRecord(event).jitterMs)).filter((value) => value !== undefined);
|
|
15607
|
+
const timestampDrift = runtimeEvents.map((event) => readNumber2(readTraceRecord(event).timestampDriftMs)).filter((value) => value !== undefined);
|
|
15608
|
+
const backpressure = runtimeEvents.map((event) => readNumber2(readTraceRecord(event).backpressureEvents)).filter((value) => value !== undefined);
|
|
15609
|
+
const interruptions = runtimeEvents.map((event) => {
|
|
15610
|
+
const payload = readTraceRecord(event);
|
|
15611
|
+
return readNumber2(payload.interruptionLatencyMs) ?? readNumber2(payload.interruptionMs) ?? readNumber2(payload.elapsedMs);
|
|
15612
|
+
}).filter((value) => value !== undefined);
|
|
15613
|
+
return {
|
|
15614
|
+
maxBackpressureEvents: maxNumber(backpressure),
|
|
15615
|
+
maxFirstAudioLatencyMs: maxNumber(firstAudio),
|
|
15616
|
+
maxInterruptionP95Ms: percentile2(interruptions, 95),
|
|
15617
|
+
maxJitterMs: maxNumber(jitter),
|
|
15618
|
+
maxTimestampDriftMs: maxNumber(timestampDrift),
|
|
15619
|
+
samples: runtimeEvents.length,
|
|
15620
|
+
status: runtimeEvents.some((event) => isFailingTraceStatus(readTraceStatus(readTraceRecord(event)))) ? "fail" : "pass"
|
|
15621
|
+
};
|
|
15622
|
+
};
|
|
15623
|
+
var buildVoiceRealCallProfileEvidenceFromTraceEvents = (events, options = {}) => {
|
|
15624
|
+
const sessionFilter = new Set(options.sessionIds ?? []);
|
|
15625
|
+
const eventsBySession = new Map;
|
|
15626
|
+
for (const event of events) {
|
|
15627
|
+
if (sessionFilter.size > 0 && !sessionFilter.has(event.sessionId)) {
|
|
15628
|
+
continue;
|
|
15629
|
+
}
|
|
15630
|
+
eventsBySession.set(event.sessionId, [
|
|
15631
|
+
...eventsBySession.get(event.sessionId) ?? [],
|
|
15632
|
+
event
|
|
15633
|
+
]);
|
|
15634
|
+
}
|
|
15635
|
+
return [...eventsBySession.entries()].map(([
|
|
15636
|
+
sessionId,
|
|
15637
|
+
sessionEvents
|
|
15638
|
+
]) => {
|
|
15639
|
+
const profileId = readTraceProfileId(sessionEvents, options);
|
|
15640
|
+
if (!profileId) {
|
|
15641
|
+
return;
|
|
15642
|
+
}
|
|
15643
|
+
const providers = summarizeProviderTraceEvidence(sessionEvents, options.maxProviderP95Ms);
|
|
15644
|
+
const liveLatencies = sessionEvents.filter((event) => event.type === "client.live_latency").map((event) => {
|
|
15645
|
+
const payload = readTraceRecord(event);
|
|
15646
|
+
return readNumber2(payload.latencyMs) ?? readNumber2(payload.elapsedMs);
|
|
15647
|
+
}).filter((value) => value !== undefined);
|
|
15648
|
+
const turnP95Ms = summarizeTurnTraceP95(sessionEvents);
|
|
15649
|
+
const providerP95Ms = maxNumber(providers.map((provider) => provider.p95Ms));
|
|
15650
|
+
const runtimeChannel = summarizeRuntimeChannelTraceEvidence(sessionEvents);
|
|
15651
|
+
return {
|
|
15652
|
+
generatedAt: new Date(Math.max(...sessionEvents.map((event) => event.at))).toISOString(),
|
|
15653
|
+
liveP95Ms: percentile2(liveLatencies, 95),
|
|
15654
|
+
ok: providers.every((provider) => provider.status !== "fail") && (runtimeChannel?.status ?? "pass") !== "fail",
|
|
15655
|
+
operationsRecordHref: `/voice-operations/${sessionId}`,
|
|
15656
|
+
profileDescription: options.profileDescriptions?.[profileId],
|
|
15657
|
+
profileId,
|
|
15658
|
+
profileLabel: options.profileLabels?.[profileId] ?? (profileId === options.defaultProfileId ? options.defaultProfileLabel : undefined),
|
|
15659
|
+
providerP95Ms,
|
|
15660
|
+
providers,
|
|
15661
|
+
runtimeChannel,
|
|
15662
|
+
sessionId,
|
|
15663
|
+
turnP95Ms
|
|
15664
|
+
};
|
|
15665
|
+
}).filter((evidence) => evidence !== undefined);
|
|
15666
|
+
};
|
|
15667
|
+
var loadVoiceRealCallProfileEvidenceFromTraceStore = async (options) => buildVoiceRealCallProfileEvidenceFromTraceEvents(await options.store.list({ limit: options.limit ?? 5000 }), options);
|
|
15511
15668
|
var readProofTrendProviders = (reports) => aggregateProofTrendProviders(reports.flatMap((report) => report.summary.providers && report.summary.providers.length > 0 ? report.summary.providers : report.cycles.flatMap((cycle) => cycle.providers ?? [])));
|
|
15512
15669
|
var exceedsProofTrendBudget = (value, budget) => value !== undefined && (!Number.isFinite(value) || value > budget);
|
|
15513
15670
|
var readProofTrendProfileStatus = (profile, budgets) => {
|
|
@@ -17189,7 +17346,7 @@ var DEFAULT_WARN_RATIO = 0.8;
|
|
|
17189
17346
|
var DEFAULT_MIN_PASSING_RUNS = 3;
|
|
17190
17347
|
var roundMs = (value) => Math.max(1, Math.ceil(value));
|
|
17191
17348
|
var finiteNumber = (value) => typeof value === "number" && Number.isFinite(value) && value >= 0;
|
|
17192
|
-
var
|
|
17349
|
+
var percentile3 = (values, rank) => {
|
|
17193
17350
|
if (values.length === 0) {
|
|
17194
17351
|
return;
|
|
17195
17352
|
}
|
|
@@ -17213,7 +17370,7 @@ var normalizeSample = (input) => {
|
|
|
17213
17370
|
return input;
|
|
17214
17371
|
};
|
|
17215
17372
|
var createThreshold = (metric, values, options) => {
|
|
17216
|
-
const baselineP95Ms =
|
|
17373
|
+
const baselineP95Ms = percentile3(values, 95);
|
|
17217
17374
|
const maxObservedMs = values.length > 0 ? Math.max(...values) : undefined;
|
|
17218
17375
|
const recommendedMs = baselineP95Ms === undefined ? undefined : roundMs(Math.max(options.minimumMs, baselineP95Ms * options.headroomMultiplier));
|
|
17219
17376
|
const warnAfterMs = recommendedMs === undefined ? undefined : roundMs(Math.max(options.minimumMs, recommendedMs * options.warnRatio));
|
|
@@ -21729,7 +21886,7 @@ var createVoiceTurnLatencyRoutes = (options) => {
|
|
|
21729
21886
|
// src/liveLatency.ts
|
|
21730
21887
|
import { Elysia as Elysia37 } from "elysia";
|
|
21731
21888
|
var escapeHtml38 = (value) => value.replaceAll("&", "&").replaceAll("<", "<").replaceAll(">", ">").replaceAll('"', """).replaceAll("'", "'");
|
|
21732
|
-
var
|
|
21889
|
+
var percentile4 = (values, percentileValue) => {
|
|
21733
21890
|
if (values.length === 0) {
|
|
21734
21891
|
return;
|
|
21735
21892
|
}
|
|
@@ -21765,8 +21922,8 @@ var summarizeVoiceLiveLatency = async (options) => {
|
|
|
21765
21922
|
averageLatencyMs: latencies.length > 0 ? Math.round(latencies.reduce((total, value) => total + value, 0) / latencies.length) : undefined,
|
|
21766
21923
|
checkedAt: Date.now(),
|
|
21767
21924
|
failed,
|
|
21768
|
-
p50LatencyMs:
|
|
21769
|
-
p95LatencyMs:
|
|
21925
|
+
p50LatencyMs: percentile4(latencies, 50),
|
|
21926
|
+
p95LatencyMs: percentile4(latencies, 95),
|
|
21770
21927
|
recent,
|
|
21771
21928
|
status: latencies.length === 0 ? "empty" : failed > 0 ? "fail" : warnings > 0 ? "warn" : "pass",
|
|
21772
21929
|
total: latencies.length,
|
|
@@ -21842,7 +21999,7 @@ var TRACE_TYPES = [
|
|
|
21842
21999
|
];
|
|
21843
22000
|
var getNumber8 = (value) => typeof value === "number" && Number.isFinite(value) ? value : undefined;
|
|
21844
22001
|
var getString14 = (value) => typeof value === "string" && value.trim() ? value : undefined;
|
|
21845
|
-
var
|
|
22002
|
+
var percentile5 = (values, percentileValue) => {
|
|
21846
22003
|
if (values.length === 0) {
|
|
21847
22004
|
return;
|
|
21848
22005
|
}
|
|
@@ -22050,8 +22207,8 @@ var summarizeStage = (stage, measurements, options) => {
|
|
|
22050
22207
|
label: STAGE_LABELS[stage],
|
|
22051
22208
|
maxMs: latencies.length > 0 ? Math.max(...latencies) : undefined,
|
|
22052
22209
|
measurements: stageMeasurements,
|
|
22053
|
-
p50Ms:
|
|
22054
|
-
p95Ms:
|
|
22210
|
+
p50Ms: percentile5(latencies, 50),
|
|
22211
|
+
p95Ms: percentile5(latencies, 95),
|
|
22055
22212
|
stage,
|
|
22056
22213
|
status: stageMeasurements.length === 0 ? "empty" : failed > 0 ? "fail" : warnings > 0 ? "warn" : "pass",
|
|
22057
22214
|
total: stageMeasurements.length,
|
|
@@ -27869,7 +28026,7 @@ var rate3 = (count, total) => count / Math.max(1, total);
|
|
|
27869
28026
|
var uniqueSorted7 = (values) => [
|
|
27870
28027
|
...new Set(values.filter((value) => typeof value === "string"))
|
|
27871
28028
|
].sort();
|
|
27872
|
-
var
|
|
28029
|
+
var percentile6 = (values, rank) => {
|
|
27873
28030
|
if (values.length === 0) {
|
|
27874
28031
|
return 0;
|
|
27875
28032
|
}
|
|
@@ -27927,7 +28084,7 @@ var summarizeKind = (kind, events, thresholds, required) => {
|
|
|
27927
28084
|
unit: "rate"
|
|
27928
28085
|
}),
|
|
27929
28086
|
p95ElapsedMs: createMetric2({
|
|
27930
|
-
actual:
|
|
28087
|
+
actual: percentile6(latencies, 95),
|
|
27931
28088
|
label: "P95 latency",
|
|
27932
28089
|
threshold: thresholds.maxP95ElapsedMs,
|
|
27933
28090
|
unit: "ms"
|
|
@@ -38256,6 +38413,7 @@ export {
|
|
|
38256
38413
|
muteVoiceMonitorIssue,
|
|
38257
38414
|
matchesVoiceOpsTaskAssignmentRule,
|
|
38258
38415
|
markVoiceOpsTaskSLABreached,
|
|
38416
|
+
loadVoiceRealCallProfileEvidenceFromTraceStore,
|
|
38259
38417
|
loadVoiceObservabilityExportReplaySource,
|
|
38260
38418
|
listVoiceRoutingEvents,
|
|
38261
38419
|
listVoiceProviderDecisionTraces,
|
|
@@ -38634,6 +38792,7 @@ export {
|
|
|
38634
38792
|
buildVoiceRealtimeChannelRuntimeSamplesFromTrace,
|
|
38635
38793
|
buildVoiceRealtimeChannelReport,
|
|
38636
38794
|
buildVoiceRealCallProfileHistoryReport,
|
|
38795
|
+
buildVoiceRealCallProfileEvidenceFromTraceEvents,
|
|
38637
38796
|
buildVoiceRealCallProfileDefaults,
|
|
38638
38797
|
buildVoiceProviderSloReport,
|
|
38639
38798
|
buildVoiceProviderOrchestrationReport,
|
package/dist/proofTrends.d.ts
CHANGED
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
import { Elysia } from 'elysia';
|
|
2
|
+
import type { StoredVoiceTraceEvent, VoiceTraceEventStore } from './trace';
|
|
2
3
|
export type VoiceProofTrendStatus = 'empty' | 'fail' | 'pass' | 'stale';
|
|
3
4
|
export type VoiceProofTrendSummary = {
|
|
4
5
|
cycles?: number;
|
|
@@ -134,6 +135,18 @@ export type VoiceProofTrendRealCallProfileEvidence = {
|
|
|
134
135
|
sessionId: string;
|
|
135
136
|
turnP95Ms?: number;
|
|
136
137
|
};
|
|
138
|
+
export type VoiceRealCallProfileTraceEvidenceOptions = {
|
|
139
|
+
defaultProfileId?: string;
|
|
140
|
+
defaultProfileLabel?: string;
|
|
141
|
+
maxProviderP95Ms?: number;
|
|
142
|
+
profileDescriptions?: Record<string, string>;
|
|
143
|
+
profileLabels?: Record<string, string>;
|
|
144
|
+
sessionIds?: readonly string[];
|
|
145
|
+
};
|
|
146
|
+
export type VoiceRealCallProfileTraceStoreEvidenceOptions = VoiceRealCallProfileTraceEvidenceOptions & {
|
|
147
|
+
limit?: number;
|
|
148
|
+
store: VoiceTraceEventStore;
|
|
149
|
+
};
|
|
137
150
|
export type VoiceProofTrendRealCallProfileReportOptions = VoiceProofTrendProfileSummaryOptions & {
|
|
138
151
|
baseUrl?: string;
|
|
139
152
|
evidence: readonly VoiceProofTrendRealCallProfileEvidence[];
|
|
@@ -370,6 +383,8 @@ export declare const normalizeVoiceProofTrendReport: (value: VoiceProofTrendRepo
|
|
|
370
383
|
export declare const readVoiceProofTrendReportFile: (path: string, options?: {
|
|
371
384
|
maxAgeMs?: number;
|
|
372
385
|
}) => Promise<VoiceProofTrendReport>;
|
|
386
|
+
export declare const buildVoiceRealCallProfileEvidenceFromTraceEvents: (events: readonly StoredVoiceTraceEvent[], options?: VoiceRealCallProfileTraceEvidenceOptions) => VoiceProofTrendRealCallProfileEvidence[];
|
|
387
|
+
export declare const loadVoiceRealCallProfileEvidenceFromTraceStore: (options: VoiceRealCallProfileTraceStoreEvidenceOptions) => Promise<VoiceProofTrendRealCallProfileEvidence[]>;
|
|
373
388
|
export declare const buildVoiceProofTrendProfileSummaries: (input: VoiceProofTrendReport | readonly VoiceProofTrendReport[], options?: VoiceProofTrendProfileSummaryOptions) => VoiceProofTrendProfileSummary[];
|
|
374
389
|
export declare const buildVoiceProofTrendReportFromRealCallProfiles: (options: VoiceProofTrendRealCallProfileReportOptions) => VoiceProofTrendReport;
|
|
375
390
|
export declare const buildVoiceRealCallProfileDefaults: (input: VoiceRealCallProfileHistoryReport | VoiceProofTrendReport, options?: VoiceRealCallProfileDefaultsOptions) => VoiceRealCallProfileDefaultsReport;
|
package/dist/react/index.js
CHANGED
|
@@ -1603,6 +1603,20 @@ var maxNumber = (values) => {
|
|
|
1603
1603
|
const finite = values.filter((value) => typeof value === "number" && Number.isFinite(value));
|
|
1604
1604
|
return finite.length > 0 ? Math.max(...finite) : undefined;
|
|
1605
1605
|
};
|
|
1606
|
+
var percentile = (values, rank) => {
|
|
1607
|
+
const finite = values.filter((value) => Number.isFinite(value)).sort((left, right) => left - right);
|
|
1608
|
+
if (finite.length === 0) {
|
|
1609
|
+
return;
|
|
1610
|
+
}
|
|
1611
|
+
const index = Math.min(finite.length - 1, Math.max(0, Math.ceil(rank / 100 * finite.length) - 1));
|
|
1612
|
+
return finite[index];
|
|
1613
|
+
};
|
|
1614
|
+
var averageNumber = (values) => {
|
|
1615
|
+
const finite = values.filter((value) => Number.isFinite(value));
|
|
1616
|
+
return finite.length === 0 ? undefined : Math.round(finite.reduce((total, value) => total + value, 0) / finite.length);
|
|
1617
|
+
};
|
|
1618
|
+
var readString = (value) => typeof value === "string" && value.trim() ? value : undefined;
|
|
1619
|
+
var readNumber2 = (value) => typeof value === "number" && Number.isFinite(value) ? value : undefined;
|
|
1606
1620
|
var readProofTrendMaxLiveP95 = (report) => report.summary.maxLiveP95Ms ?? maxNumber(report.cycles.map((cycle) => cycle.liveLatency?.p95Ms));
|
|
1607
1621
|
var readProofTrendMaxProviderP95 = (report) => report.summary.maxProviderP95Ms ?? maxNumber((report.summary.providers ?? []).map((provider) => provider.p95Ms)) ?? maxNumber(report.cycles.flatMap((cycle) => (cycle.providers ?? []).map((provider) => provider.p95Ms)));
|
|
1608
1622
|
var readProofTrendMaxTurnP95 = (report) => report.summary.maxTurnP95Ms ?? maxNumber(report.cycles.map((cycle) => cycle.turnLatency?.p95Ms));
|
|
@@ -1666,6 +1680,149 @@ var aggregateProofTrendRuntimeChannel = (channels) => {
|
|
|
1666
1680
|
status: channels.some((channel) => channel.status === "fail") ? "fail" : channels.some((channel) => channel.status === "warn") ? "warn" : channels.every((channel) => channel.status === "pass") ? "pass" : undefined
|
|
1667
1681
|
};
|
|
1668
1682
|
};
|
|
1683
|
+
var readTraceRecord = (event) => event.payload;
|
|
1684
|
+
var readTraceProfileId = (events, options) => {
|
|
1685
|
+
for (const event of events) {
|
|
1686
|
+
const payload = readTraceRecord(event);
|
|
1687
|
+
const profileId = readString(payload.profileId) ?? readString(event.metadata?.profileId) ?? readString(payload.benchmarkProfileId) ?? readString(event.metadata?.benchmarkProfileId);
|
|
1688
|
+
if (profileId) {
|
|
1689
|
+
return profileId;
|
|
1690
|
+
}
|
|
1691
|
+
}
|
|
1692
|
+
return options.defaultProfileId;
|
|
1693
|
+
};
|
|
1694
|
+
var readProviderTraceRole = (payload) => readString(payload.kind) ?? readString(payload.role) ?? readString(payload.surface) ?? "provider";
|
|
1695
|
+
var readProviderTraceLatency = (payload) => readNumber2(payload.elapsedMs) ?? readNumber2(payload.latencyMs) ?? readNumber2(payload.durationMs);
|
|
1696
|
+
var readProviderTraceId = (payload) => readString(payload.selectedProvider) ?? readString(payload.provider) ?? readString(payload.model) ?? readString(payload.adapter);
|
|
1697
|
+
var readTraceStatus = (payload) => readString(payload.providerStatus) ?? readString(payload.status);
|
|
1698
|
+
var isFailingTraceStatus = (status) => status === "error" || status === "fail" || status === "failed" || status === "timeout";
|
|
1699
|
+
var summarizeProviderTraceEvidence = (events, maxProviderP95Ms) => {
|
|
1700
|
+
const providerLatencies = new Map;
|
|
1701
|
+
const providerMeta = new Map;
|
|
1702
|
+
for (const event of events) {
|
|
1703
|
+
if (event.type !== "session.error" && event.type !== "provider.decision") {
|
|
1704
|
+
continue;
|
|
1705
|
+
}
|
|
1706
|
+
const payload = readTraceRecord(event);
|
|
1707
|
+
const provider = readProviderTraceId(payload);
|
|
1708
|
+
if (!provider) {
|
|
1709
|
+
continue;
|
|
1710
|
+
}
|
|
1711
|
+
const role = readProviderTraceRole(payload);
|
|
1712
|
+
const id = `${role}:${provider}`;
|
|
1713
|
+
const latency = readProviderTraceLatency(payload);
|
|
1714
|
+
if (latency !== undefined) {
|
|
1715
|
+
providerLatencies.set(id, [...providerLatencies.get(id) ?? [], latency]);
|
|
1716
|
+
}
|
|
1717
|
+
const existing = providerMeta.get(id);
|
|
1718
|
+
providerMeta.set(id, {
|
|
1719
|
+
failed: existing?.failed === true || isFailingTraceStatus(readTraceStatus(payload)),
|
|
1720
|
+
label: existing?.label ?? `${role.toUpperCase()} ${provider}`,
|
|
1721
|
+
role: existing?.role ?? role
|
|
1722
|
+
});
|
|
1723
|
+
}
|
|
1724
|
+
return [...providerMeta.entries()].map(([id, meta]) => {
|
|
1725
|
+
const latencies = providerLatencies.get(id) ?? [];
|
|
1726
|
+
const p95Ms = percentile(latencies, 95);
|
|
1727
|
+
return {
|
|
1728
|
+
averageMs: averageNumber(latencies),
|
|
1729
|
+
id,
|
|
1730
|
+
label: meta.label,
|
|
1731
|
+
p50Ms: percentile(latencies, 50),
|
|
1732
|
+
p95Ms,
|
|
1733
|
+
role: meta.role,
|
|
1734
|
+
samples: latencies.length,
|
|
1735
|
+
status: meta.failed || (p95Ms ?? 0) > (maxProviderP95Ms ?? Number.POSITIVE_INFINITY) ? "fail" : latencies.length > 0 ? "pass" : "warn"
|
|
1736
|
+
};
|
|
1737
|
+
});
|
|
1738
|
+
};
|
|
1739
|
+
var summarizeTurnTraceP95 = (events) => {
|
|
1740
|
+
const explicit = events.filter((event) => event.type === "turn_latency.stage").map((event) => {
|
|
1741
|
+
const payload = readTraceRecord(event);
|
|
1742
|
+
return readNumber2(payload.totalMs) ?? readNumber2(payload.elapsedMs) ?? readNumber2(payload.latencyMs);
|
|
1743
|
+
}).filter((value) => value !== undefined);
|
|
1744
|
+
if (explicit.length > 0) {
|
|
1745
|
+
return percentile(explicit, 95);
|
|
1746
|
+
}
|
|
1747
|
+
const turnStages = new Map;
|
|
1748
|
+
for (const event of events) {
|
|
1749
|
+
if (event.type !== "turn_latency.stage" || !event.turnId) {
|
|
1750
|
+
continue;
|
|
1751
|
+
}
|
|
1752
|
+
const key = `${event.sessionId}:${event.turnId}`;
|
|
1753
|
+
turnStages.set(key, [...turnStages.get(key) ?? [], event.at]);
|
|
1754
|
+
}
|
|
1755
|
+
const totals = [...turnStages.values()].map((stages) => stages.length < 2 ? undefined : Math.max(...stages) - Math.min(...stages)).filter((value) => value !== undefined);
|
|
1756
|
+
return percentile(totals, 95);
|
|
1757
|
+
};
|
|
1758
|
+
var summarizeRuntimeChannelTraceEvidence = (events) => {
|
|
1759
|
+
const runtimeEvents = events.filter((event) => event.type === "client.browser_media" || event.type === "client.telephony_media" || event.type === "client.barge_in");
|
|
1760
|
+
if (runtimeEvents.length === 0) {
|
|
1761
|
+
return;
|
|
1762
|
+
}
|
|
1763
|
+
const firstAudio = runtimeEvents.map((event) => readNumber2(readTraceRecord(event).firstAudioLatencyMs)).filter((value) => value !== undefined);
|
|
1764
|
+
const jitter = runtimeEvents.map((event) => readNumber2(readTraceRecord(event).jitterMs)).filter((value) => value !== undefined);
|
|
1765
|
+
const timestampDrift = runtimeEvents.map((event) => readNumber2(readTraceRecord(event).timestampDriftMs)).filter((value) => value !== undefined);
|
|
1766
|
+
const backpressure = runtimeEvents.map((event) => readNumber2(readTraceRecord(event).backpressureEvents)).filter((value) => value !== undefined);
|
|
1767
|
+
const interruptions = runtimeEvents.map((event) => {
|
|
1768
|
+
const payload = readTraceRecord(event);
|
|
1769
|
+
return readNumber2(payload.interruptionLatencyMs) ?? readNumber2(payload.interruptionMs) ?? readNumber2(payload.elapsedMs);
|
|
1770
|
+
}).filter((value) => value !== undefined);
|
|
1771
|
+
return {
|
|
1772
|
+
maxBackpressureEvents: maxNumber(backpressure),
|
|
1773
|
+
maxFirstAudioLatencyMs: maxNumber(firstAudio),
|
|
1774
|
+
maxInterruptionP95Ms: percentile(interruptions, 95),
|
|
1775
|
+
maxJitterMs: maxNumber(jitter),
|
|
1776
|
+
maxTimestampDriftMs: maxNumber(timestampDrift),
|
|
1777
|
+
samples: runtimeEvents.length,
|
|
1778
|
+
status: runtimeEvents.some((event) => isFailingTraceStatus(readTraceStatus(readTraceRecord(event)))) ? "fail" : "pass"
|
|
1779
|
+
};
|
|
1780
|
+
};
|
|
1781
|
+
var buildVoiceRealCallProfileEvidenceFromTraceEvents = (events, options = {}) => {
|
|
1782
|
+
const sessionFilter = new Set(options.sessionIds ?? []);
|
|
1783
|
+
const eventsBySession = new Map;
|
|
1784
|
+
for (const event of events) {
|
|
1785
|
+
if (sessionFilter.size > 0 && !sessionFilter.has(event.sessionId)) {
|
|
1786
|
+
continue;
|
|
1787
|
+
}
|
|
1788
|
+
eventsBySession.set(event.sessionId, [
|
|
1789
|
+
...eventsBySession.get(event.sessionId) ?? [],
|
|
1790
|
+
event
|
|
1791
|
+
]);
|
|
1792
|
+
}
|
|
1793
|
+
return [...eventsBySession.entries()].map(([
|
|
1794
|
+
sessionId,
|
|
1795
|
+
sessionEvents
|
|
1796
|
+
]) => {
|
|
1797
|
+
const profileId = readTraceProfileId(sessionEvents, options);
|
|
1798
|
+
if (!profileId) {
|
|
1799
|
+
return;
|
|
1800
|
+
}
|
|
1801
|
+
const providers = summarizeProviderTraceEvidence(sessionEvents, options.maxProviderP95Ms);
|
|
1802
|
+
const liveLatencies = sessionEvents.filter((event) => event.type === "client.live_latency").map((event) => {
|
|
1803
|
+
const payload = readTraceRecord(event);
|
|
1804
|
+
return readNumber2(payload.latencyMs) ?? readNumber2(payload.elapsedMs);
|
|
1805
|
+
}).filter((value) => value !== undefined);
|
|
1806
|
+
const turnP95Ms = summarizeTurnTraceP95(sessionEvents);
|
|
1807
|
+
const providerP95Ms = maxNumber(providers.map((provider) => provider.p95Ms));
|
|
1808
|
+
const runtimeChannel = summarizeRuntimeChannelTraceEvidence(sessionEvents);
|
|
1809
|
+
return {
|
|
1810
|
+
generatedAt: new Date(Math.max(...sessionEvents.map((event) => event.at))).toISOString(),
|
|
1811
|
+
liveP95Ms: percentile(liveLatencies, 95),
|
|
1812
|
+
ok: providers.every((provider) => provider.status !== "fail") && (runtimeChannel?.status ?? "pass") !== "fail",
|
|
1813
|
+
operationsRecordHref: `/voice-operations/${sessionId}`,
|
|
1814
|
+
profileDescription: options.profileDescriptions?.[profileId],
|
|
1815
|
+
profileId,
|
|
1816
|
+
profileLabel: options.profileLabels?.[profileId] ?? (profileId === options.defaultProfileId ? options.defaultProfileLabel : undefined),
|
|
1817
|
+
providerP95Ms,
|
|
1818
|
+
providers,
|
|
1819
|
+
runtimeChannel,
|
|
1820
|
+
sessionId,
|
|
1821
|
+
turnP95Ms
|
|
1822
|
+
};
|
|
1823
|
+
}).filter((evidence) => evidence !== undefined);
|
|
1824
|
+
};
|
|
1825
|
+
var loadVoiceRealCallProfileEvidenceFromTraceStore = async (options) => buildVoiceRealCallProfileEvidenceFromTraceEvents(await options.store.list({ limit: options.limit ?? 5000 }), options);
|
|
1669
1826
|
var readProofTrendProviders = (reports) => aggregateProofTrendProviders(reports.flatMap((report) => report.summary.providers && report.summary.providers.length > 0 ? report.summary.providers : report.cycles.flatMap((cycle) => cycle.providers ?? [])));
|
|
1670
1827
|
var exceedsProofTrendBudget = (value, budget) => value !== undefined && (!Number.isFinite(value) || value > budget);
|
|
1671
1828
|
var readProofTrendProfileStatus = (profile, budgets) => {
|
package/dist/vue/index.js
CHANGED
|
@@ -1524,6 +1524,20 @@ var maxNumber = (values) => {
|
|
|
1524
1524
|
const finite = values.filter((value) => typeof value === "number" && Number.isFinite(value));
|
|
1525
1525
|
return finite.length > 0 ? Math.max(...finite) : undefined;
|
|
1526
1526
|
};
|
|
1527
|
+
var percentile = (values, rank) => {
|
|
1528
|
+
const finite = values.filter((value) => Number.isFinite(value)).sort((left, right) => left - right);
|
|
1529
|
+
if (finite.length === 0) {
|
|
1530
|
+
return;
|
|
1531
|
+
}
|
|
1532
|
+
const index = Math.min(finite.length - 1, Math.max(0, Math.ceil(rank / 100 * finite.length) - 1));
|
|
1533
|
+
return finite[index];
|
|
1534
|
+
};
|
|
1535
|
+
var averageNumber = (values) => {
|
|
1536
|
+
const finite = values.filter((value) => Number.isFinite(value));
|
|
1537
|
+
return finite.length === 0 ? undefined : Math.round(finite.reduce((total, value) => total + value, 0) / finite.length);
|
|
1538
|
+
};
|
|
1539
|
+
var readString = (value) => typeof value === "string" && value.trim() ? value : undefined;
|
|
1540
|
+
var readNumber2 = (value) => typeof value === "number" && Number.isFinite(value) ? value : undefined;
|
|
1527
1541
|
var readProofTrendMaxLiveP95 = (report) => report.summary.maxLiveP95Ms ?? maxNumber(report.cycles.map((cycle) => cycle.liveLatency?.p95Ms));
|
|
1528
1542
|
var readProofTrendMaxProviderP95 = (report) => report.summary.maxProviderP95Ms ?? maxNumber((report.summary.providers ?? []).map((provider) => provider.p95Ms)) ?? maxNumber(report.cycles.flatMap((cycle) => (cycle.providers ?? []).map((provider) => provider.p95Ms)));
|
|
1529
1543
|
var readProofTrendMaxTurnP95 = (report) => report.summary.maxTurnP95Ms ?? maxNumber(report.cycles.map((cycle) => cycle.turnLatency?.p95Ms));
|
|
@@ -1587,6 +1601,149 @@ var aggregateProofTrendRuntimeChannel = (channels) => {
|
|
|
1587
1601
|
status: channels.some((channel) => channel.status === "fail") ? "fail" : channels.some((channel) => channel.status === "warn") ? "warn" : channels.every((channel) => channel.status === "pass") ? "pass" : undefined
|
|
1588
1602
|
};
|
|
1589
1603
|
};
|
|
1604
|
+
var readTraceRecord = (event) => event.payload;
|
|
1605
|
+
var readTraceProfileId = (events, options) => {
|
|
1606
|
+
for (const event of events) {
|
|
1607
|
+
const payload = readTraceRecord(event);
|
|
1608
|
+
const profileId = readString(payload.profileId) ?? readString(event.metadata?.profileId) ?? readString(payload.benchmarkProfileId) ?? readString(event.metadata?.benchmarkProfileId);
|
|
1609
|
+
if (profileId) {
|
|
1610
|
+
return profileId;
|
|
1611
|
+
}
|
|
1612
|
+
}
|
|
1613
|
+
return options.defaultProfileId;
|
|
1614
|
+
};
|
|
1615
|
+
var readProviderTraceRole = (payload) => readString(payload.kind) ?? readString(payload.role) ?? readString(payload.surface) ?? "provider";
|
|
1616
|
+
var readProviderTraceLatency = (payload) => readNumber2(payload.elapsedMs) ?? readNumber2(payload.latencyMs) ?? readNumber2(payload.durationMs);
|
|
1617
|
+
var readProviderTraceId = (payload) => readString(payload.selectedProvider) ?? readString(payload.provider) ?? readString(payload.model) ?? readString(payload.adapter);
|
|
1618
|
+
var readTraceStatus = (payload) => readString(payload.providerStatus) ?? readString(payload.status);
|
|
1619
|
+
var isFailingTraceStatus = (status) => status === "error" || status === "fail" || status === "failed" || status === "timeout";
|
|
1620
|
+
var summarizeProviderTraceEvidence = (events, maxProviderP95Ms) => {
|
|
1621
|
+
const providerLatencies = new Map;
|
|
1622
|
+
const providerMeta = new Map;
|
|
1623
|
+
for (const event of events) {
|
|
1624
|
+
if (event.type !== "session.error" && event.type !== "provider.decision") {
|
|
1625
|
+
continue;
|
|
1626
|
+
}
|
|
1627
|
+
const payload = readTraceRecord(event);
|
|
1628
|
+
const provider = readProviderTraceId(payload);
|
|
1629
|
+
if (!provider) {
|
|
1630
|
+
continue;
|
|
1631
|
+
}
|
|
1632
|
+
const role = readProviderTraceRole(payload);
|
|
1633
|
+
const id = `${role}:${provider}`;
|
|
1634
|
+
const latency = readProviderTraceLatency(payload);
|
|
1635
|
+
if (latency !== undefined) {
|
|
1636
|
+
providerLatencies.set(id, [...providerLatencies.get(id) ?? [], latency]);
|
|
1637
|
+
}
|
|
1638
|
+
const existing = providerMeta.get(id);
|
|
1639
|
+
providerMeta.set(id, {
|
|
1640
|
+
failed: existing?.failed === true || isFailingTraceStatus(readTraceStatus(payload)),
|
|
1641
|
+
label: existing?.label ?? `${role.toUpperCase()} ${provider}`,
|
|
1642
|
+
role: existing?.role ?? role
|
|
1643
|
+
});
|
|
1644
|
+
}
|
|
1645
|
+
return [...providerMeta.entries()].map(([id, meta]) => {
|
|
1646
|
+
const latencies = providerLatencies.get(id) ?? [];
|
|
1647
|
+
const p95Ms = percentile(latencies, 95);
|
|
1648
|
+
return {
|
|
1649
|
+
averageMs: averageNumber(latencies),
|
|
1650
|
+
id,
|
|
1651
|
+
label: meta.label,
|
|
1652
|
+
p50Ms: percentile(latencies, 50),
|
|
1653
|
+
p95Ms,
|
|
1654
|
+
role: meta.role,
|
|
1655
|
+
samples: latencies.length,
|
|
1656
|
+
status: meta.failed || (p95Ms ?? 0) > (maxProviderP95Ms ?? Number.POSITIVE_INFINITY) ? "fail" : latencies.length > 0 ? "pass" : "warn"
|
|
1657
|
+
};
|
|
1658
|
+
});
|
|
1659
|
+
};
|
|
1660
|
+
var summarizeTurnTraceP95 = (events) => {
|
|
1661
|
+
const explicit = events.filter((event) => event.type === "turn_latency.stage").map((event) => {
|
|
1662
|
+
const payload = readTraceRecord(event);
|
|
1663
|
+
return readNumber2(payload.totalMs) ?? readNumber2(payload.elapsedMs) ?? readNumber2(payload.latencyMs);
|
|
1664
|
+
}).filter((value) => value !== undefined);
|
|
1665
|
+
if (explicit.length > 0) {
|
|
1666
|
+
return percentile(explicit, 95);
|
|
1667
|
+
}
|
|
1668
|
+
const turnStages = new Map;
|
|
1669
|
+
for (const event of events) {
|
|
1670
|
+
if (event.type !== "turn_latency.stage" || !event.turnId) {
|
|
1671
|
+
continue;
|
|
1672
|
+
}
|
|
1673
|
+
const key = `${event.sessionId}:${event.turnId}`;
|
|
1674
|
+
turnStages.set(key, [...turnStages.get(key) ?? [], event.at]);
|
|
1675
|
+
}
|
|
1676
|
+
const totals = [...turnStages.values()].map((stages) => stages.length < 2 ? undefined : Math.max(...stages) - Math.min(...stages)).filter((value) => value !== undefined);
|
|
1677
|
+
return percentile(totals, 95);
|
|
1678
|
+
};
|
|
1679
|
+
var summarizeRuntimeChannelTraceEvidence = (events) => {
|
|
1680
|
+
const runtimeEvents = events.filter((event) => event.type === "client.browser_media" || event.type === "client.telephony_media" || event.type === "client.barge_in");
|
|
1681
|
+
if (runtimeEvents.length === 0) {
|
|
1682
|
+
return;
|
|
1683
|
+
}
|
|
1684
|
+
const firstAudio = runtimeEvents.map((event) => readNumber2(readTraceRecord(event).firstAudioLatencyMs)).filter((value) => value !== undefined);
|
|
1685
|
+
const jitter = runtimeEvents.map((event) => readNumber2(readTraceRecord(event).jitterMs)).filter((value) => value !== undefined);
|
|
1686
|
+
const timestampDrift = runtimeEvents.map((event) => readNumber2(readTraceRecord(event).timestampDriftMs)).filter((value) => value !== undefined);
|
|
1687
|
+
const backpressure = runtimeEvents.map((event) => readNumber2(readTraceRecord(event).backpressureEvents)).filter((value) => value !== undefined);
|
|
1688
|
+
const interruptions = runtimeEvents.map((event) => {
|
|
1689
|
+
const payload = readTraceRecord(event);
|
|
1690
|
+
return readNumber2(payload.interruptionLatencyMs) ?? readNumber2(payload.interruptionMs) ?? readNumber2(payload.elapsedMs);
|
|
1691
|
+
}).filter((value) => value !== undefined);
|
|
1692
|
+
return {
|
|
1693
|
+
maxBackpressureEvents: maxNumber(backpressure),
|
|
1694
|
+
maxFirstAudioLatencyMs: maxNumber(firstAudio),
|
|
1695
|
+
maxInterruptionP95Ms: percentile(interruptions, 95),
|
|
1696
|
+
maxJitterMs: maxNumber(jitter),
|
|
1697
|
+
maxTimestampDriftMs: maxNumber(timestampDrift),
|
|
1698
|
+
samples: runtimeEvents.length,
|
|
1699
|
+
status: runtimeEvents.some((event) => isFailingTraceStatus(readTraceStatus(readTraceRecord(event)))) ? "fail" : "pass"
|
|
1700
|
+
};
|
|
1701
|
+
};
|
|
1702
|
+
var buildVoiceRealCallProfileEvidenceFromTraceEvents = (events, options = {}) => {
|
|
1703
|
+
const sessionFilter = new Set(options.sessionIds ?? []);
|
|
1704
|
+
const eventsBySession = new Map;
|
|
1705
|
+
for (const event of events) {
|
|
1706
|
+
if (sessionFilter.size > 0 && !sessionFilter.has(event.sessionId)) {
|
|
1707
|
+
continue;
|
|
1708
|
+
}
|
|
1709
|
+
eventsBySession.set(event.sessionId, [
|
|
1710
|
+
...eventsBySession.get(event.sessionId) ?? [],
|
|
1711
|
+
event
|
|
1712
|
+
]);
|
|
1713
|
+
}
|
|
1714
|
+
return [...eventsBySession.entries()].map(([
|
|
1715
|
+
sessionId,
|
|
1716
|
+
sessionEvents
|
|
1717
|
+
]) => {
|
|
1718
|
+
const profileId = readTraceProfileId(sessionEvents, options);
|
|
1719
|
+
if (!profileId) {
|
|
1720
|
+
return;
|
|
1721
|
+
}
|
|
1722
|
+
const providers = summarizeProviderTraceEvidence(sessionEvents, options.maxProviderP95Ms);
|
|
1723
|
+
const liveLatencies = sessionEvents.filter((event) => event.type === "client.live_latency").map((event) => {
|
|
1724
|
+
const payload = readTraceRecord(event);
|
|
1725
|
+
return readNumber2(payload.latencyMs) ?? readNumber2(payload.elapsedMs);
|
|
1726
|
+
}).filter((value) => value !== undefined);
|
|
1727
|
+
const turnP95Ms = summarizeTurnTraceP95(sessionEvents);
|
|
1728
|
+
const providerP95Ms = maxNumber(providers.map((provider) => provider.p95Ms));
|
|
1729
|
+
const runtimeChannel = summarizeRuntimeChannelTraceEvidence(sessionEvents);
|
|
1730
|
+
return {
|
|
1731
|
+
generatedAt: new Date(Math.max(...sessionEvents.map((event) => event.at))).toISOString(),
|
|
1732
|
+
liveP95Ms: percentile(liveLatencies, 95),
|
|
1733
|
+
ok: providers.every((provider) => provider.status !== "fail") && (runtimeChannel?.status ?? "pass") !== "fail",
|
|
1734
|
+
operationsRecordHref: `/voice-operations/${sessionId}`,
|
|
1735
|
+
profileDescription: options.profileDescriptions?.[profileId],
|
|
1736
|
+
profileId,
|
|
1737
|
+
profileLabel: options.profileLabels?.[profileId] ?? (profileId === options.defaultProfileId ? options.defaultProfileLabel : undefined),
|
|
1738
|
+
providerP95Ms,
|
|
1739
|
+
providers,
|
|
1740
|
+
runtimeChannel,
|
|
1741
|
+
sessionId,
|
|
1742
|
+
turnP95Ms
|
|
1743
|
+
};
|
|
1744
|
+
}).filter((evidence) => evidence !== undefined);
|
|
1745
|
+
};
|
|
1746
|
+
var loadVoiceRealCallProfileEvidenceFromTraceStore = async (options) => buildVoiceRealCallProfileEvidenceFromTraceEvents(await options.store.list({ limit: options.limit ?? 5000 }), options);
|
|
1590
1747
|
var readProofTrendProviders = (reports) => aggregateProofTrendProviders(reports.flatMap((report) => report.summary.providers && report.summary.providers.length > 0 ? report.summary.providers : report.cycles.flatMap((cycle) => cycle.providers ?? [])));
|
|
1591
1748
|
var exceedsProofTrendBudget = (value, budget) => value !== undefined && (!Number.isFinite(value) || value > budget);
|
|
1592
1749
|
var readProofTrendProfileStatus = (profile, budgets) => {
|