@absolutejs/voice 0.0.22-beta.367 → 0.0.22-beta.369

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 CHANGED
@@ -1401,6 +1401,107 @@ 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
+
1489
+ Use `createVoiceProfileTraceTagger(...)` when the app already has a trace store and needs every appended trace to carry a benchmark profile label. It wraps any `VoiceTraceEventStore`, preserves the underlying store behavior, and adds `profileId`/`benchmarkProfileId` metadata and payload fields that real-call profile history can ingest later.
1490
+
1491
+ ```ts
1492
+ import { createVoiceProfileTraceTagger } from '@absolutejs/voice';
1493
+
1494
+ const trace = createVoiceProfileTraceTagger({
1495
+ defaultProfile: {
1496
+ id: 'meeting-recorder',
1497
+ label: 'Meeting recorder'
1498
+ },
1499
+ resolveProfile: (event) =>
1500
+ event.sessionId.startsWith('support-') ? 'support-agent' : undefined,
1501
+ store: runtime.traces
1502
+ });
1503
+ ```
1504
+
1404
1505
  Built-in profiles:
1405
1506
 
1406
1507
  - `meeting-recorder`: live latency, session health, provider fallback, routing contracts, reconnect proof, and barge-in interruption proof.
@@ -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) => {
@@ -5924,6 +6081,46 @@ var createVoiceTraceSinkStore = (options) => {
5924
6081
  remove: (id) => options.store.remove(id)
5925
6082
  };
5926
6083
  };
6084
+ var normalizeVoiceProfileTraceTaggerProfile = (profile) => typeof profile === "string" ? { id: profile } : profile?.id ? profile : undefined;
6085
+ var createVoiceProfileTraceTagger = (options) => {
6086
+ const profiles = new Map((options.profiles ?? []).map((profile) => [profile.id, profile]));
6087
+ const defaultProfile = normalizeVoiceProfileTraceTaggerProfile(options.defaultProfile);
6088
+ const resolveProfile = async (event) => {
6089
+ const resolved = normalizeVoiceProfileTraceTaggerProfile(await options.resolveProfile?.(event));
6090
+ const profile = resolved ?? defaultProfile;
6091
+ return profile ? profiles.get(profile.id) ?? profile : undefined;
6092
+ };
6093
+ return {
6094
+ append: async (event) => {
6095
+ const profile = await resolveProfile(event);
6096
+ if (!profile) {
6097
+ return options.store.append(event);
6098
+ }
6099
+ const metadata = {
6100
+ ...event.metadata ?? {},
6101
+ benchmarkProfileId: profile.id,
6102
+ profileDescription: event.metadata?.profileDescription ?? profile.description,
6103
+ profileId: profile.id,
6104
+ profileLabel: event.metadata?.profileLabel ?? profile.label
6105
+ };
6106
+ const payload = event.payload && typeof event.payload === "object" ? {
6107
+ ...event.payload,
6108
+ benchmarkProfileId: event.payload.benchmarkProfileId ?? profile.id,
6109
+ profileDescription: event.payload.profileDescription ?? profile.description,
6110
+ profileId: event.payload.profileId ?? profile.id,
6111
+ profileLabel: event.payload.profileLabel ?? profile.label
6112
+ } : event.payload;
6113
+ return options.store.append({
6114
+ ...event,
6115
+ metadata,
6116
+ payload
6117
+ });
6118
+ },
6119
+ get: (id) => options.store.get(id),
6120
+ list: (filter) => options.store.list(filter),
6121
+ remove: (id) => options.store.remove(id)
6122
+ };
6123
+ };
5927
6124
  var createVoiceMemoryTraceSinkDeliveryStore = () => {
5928
6125
  const deliveries = new Map;
5929
6126
  return {
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';
@@ -86,7 +86,7 @@ export { createVoiceOpsStatusRoutes, renderVoiceOpsStatusHTML } from './opsStatu
86
86
  export { createVoiceQualityRoutes, evaluateVoiceQuality, renderVoiceQualityHTML } from './qualityRoutes';
87
87
  export { createVoiceResilienceRoutes, createVoiceRoutingDecisionSummary, listVoiceRoutingEvents, renderVoiceResilienceHTML, summarizeVoiceRoutingDecision, summarizeVoiceRoutingSessions } from './resilienceRoutes';
88
88
  export { createVoiceSTTProviderRouter, createVoiceTTSProviderRouter } from './providerAdapters';
89
- export { buildVoiceTraceReplay, createVoiceMemoryTraceSinkDeliveryStore, createVoiceTraceHTTPSink, createVoiceTraceS3Sink, createVoiceMemoryTraceEventStore, createVoiceTraceSinkDeliveryId, createVoiceTraceSinkDeliveryRecord, createVoiceTraceSinkStore, createVoiceTraceEvent, createVoiceTraceEventId, deliverVoiceTraceEventsToSinks, evaluateVoiceTrace, exportVoiceTrace, filterVoiceTraceEvents, pruneVoiceTraceEvents, redactVoiceTraceEvent, redactVoiceTraceEvents, redactVoiceTraceText, renderVoiceTraceHTML, renderVoiceTraceMarkdown, resolveVoiceTraceRedactionOptions, selectVoiceTraceEventsForPrune, summarizeVoiceTrace } from './trace';
89
+ export { buildVoiceTraceReplay, createVoiceMemoryTraceSinkDeliveryStore, createVoiceProfileTraceTagger, createVoiceTraceHTTPSink, createVoiceTraceS3Sink, createVoiceMemoryTraceEventStore, createVoiceTraceSinkDeliveryId, createVoiceTraceSinkDeliveryRecord, createVoiceTraceSinkStore, createVoiceTraceEvent, createVoiceTraceEventId, deliverVoiceTraceEventsToSinks, evaluateVoiceTrace, exportVoiceTrace, filterVoiceTraceEvents, pruneVoiceTraceEvents, redactVoiceTraceEvent, redactVoiceTraceEvents, redactVoiceTraceText, renderVoiceTraceHTML, renderVoiceTraceMarkdown, resolveVoiceTraceRedactionOptions, selectVoiceTraceEventsForPrune, summarizeVoiceTrace } from './trace';
90
90
  export { buildVoiceTraceDeliveryReport, createVoiceTraceDeliveryHTMLHandler, createVoiceTraceDeliveryJSONHandler, createVoiceTraceDeliveryRoutes, renderVoiceTraceDeliveryHTML, resolveVoiceTraceDeliveryFilter } from './traceDeliveryRoutes';
91
91
  export { createVoiceTraceTimelineRoutes, renderVoiceTraceTimelineHTML, renderVoiceTraceTimelineSessionHTML, summarizeVoiceTraceTimeline } from './traceTimeline';
92
92
  export { createVoiceSQLiteAuditEventStore, createVoiceSQLiteAuditSinkDeliveryStore, createVoiceSQLiteCampaignStore, createVoiceSQLiteExternalObjectMapStore, createVoiceSQLiteIntegrationEventStore, createVoiceSQLiteReviewStore, createVoiceSQLiteRuntimeStorage, createVoiceSQLiteSessionStore, createVoiceSQLiteTaskStore, createVoiceSQLiteTelephonyWebhookIdempotencyStore, createVoiceSQLiteTraceSinkDeliveryStore, createVoiceSQLiteTraceEventStore } from './sqliteStore';
@@ -174,7 +174,7 @@ export type { VoiceAuditExport } from './auditExport';
174
174
  export type { VoiceAuditHTTPSinkOptions, VoiceAuditS3SinkOptions, VoiceS3AuditSinkClient, VoiceS3AuditSinkFile, VoiceAuditSink, VoiceAuditSinkDeliveryQueueStatus, VoiceAuditSinkDeliveryQueueSummary, VoiceAuditSinkDeliveryRecord, VoiceAuditSinkDeliveryResult, VoiceAuditSinkDeliveryStatus, VoiceAuditSinkDeliveryStore, VoiceAuditSinkDeliveryWorkerLoop, VoiceAuditSinkDeliveryWorkerLoopOptions, VoiceAuditSinkDeliveryWorkerOptions, VoiceAuditSinkDeliveryWorkerResult, VoiceAuditSinkFanoutResult, VoiceAuditSinkStoreOptions } from './auditSinks';
175
175
  export type { VoiceAuditDeliveryDrainReport, VoiceAuditDeliveryDrainWorker, VoiceAuditDeliveryFilter, VoiceAuditDeliveryReport, VoiceAuditDeliveryRoutesOptions } from './auditDeliveryRoutes';
176
176
  export type { VoiceFileRuntimeStorage, VoiceFileStoreOptions } from './fileStore';
177
- export type { StoredVoiceTraceEvent, VoiceTraceEvaluation, VoiceTraceEvaluationOptions, VoiceTraceEvent, VoiceTraceEventFilter, VoiceTraceEventStore, VoiceTraceEventType, VoiceTraceIssue, VoiceTraceIssueSeverity, VoiceTraceHTTPSinkOptions, VoiceTraceS3SinkOptions, VoiceTracePruneFilter, VoiceTracePruneOptions, VoiceTracePruneResult, VoiceTraceRedactionConfig, VoiceTraceRedactionOptions, VoiceTraceRedactionReplacement, VoiceResolvedTraceRedactionOptions, VoiceTraceSink, VoiceTraceSinkDeliveryQueueStatus, VoiceTraceSinkDeliveryRecord, VoiceTraceSinkDeliveryResult, VoiceTraceSinkDeliveryStatus, VoiceTraceSinkDeliveryStore, VoiceTraceSinkFanoutResult, VoiceTraceSinkStoreOptions, VoiceTraceSummary, VoiceS3TraceSinkClient, VoiceS3TraceSinkFile } from './trace';
177
+ export type { VoiceProfileTraceTaggerOptions, VoiceProfileTraceTaggerProfile, StoredVoiceTraceEvent, VoiceTraceEvaluation, VoiceTraceEvaluationOptions, VoiceTraceEvent, VoiceTraceEventFilter, VoiceTraceEventStore, VoiceTraceEventType, VoiceTraceIssue, VoiceTraceIssueSeverity, VoiceTraceHTTPSinkOptions, VoiceTraceS3SinkOptions, VoiceTracePruneFilter, VoiceTracePruneOptions, VoiceTracePruneResult, VoiceTraceRedactionConfig, VoiceTraceRedactionOptions, VoiceTraceRedactionReplacement, VoiceResolvedTraceRedactionOptions, VoiceTraceSink, VoiceTraceSinkDeliveryQueueStatus, VoiceTraceSinkDeliveryRecord, VoiceTraceSinkDeliveryResult, VoiceTraceSinkDeliveryStatus, VoiceTraceSinkDeliveryStore, VoiceTraceSinkFanoutResult, VoiceTraceSinkStoreOptions, VoiceTraceSummary, VoiceS3TraceSinkClient, VoiceS3TraceSinkFile } from './trace';
178
178
  export type { VoiceTraceDeliveryDrainReport, VoiceTraceDeliveryDrainWorker, VoiceTraceDeliveryFilter, VoiceTraceDeliveryReport, VoiceTraceDeliveryRoutesOptions } from './traceDeliveryRoutes';
179
179
  export type { VoiceTraceTimelineEvent, VoiceTraceTimelineProviderSummary, VoiceTraceTimelineReport, VoiceTraceTimelineRoutesOptions, VoiceTraceTimelineSession } from './traceTimeline';
180
180
  export type { VoicePostgresClient, VoicePostgresRuntimeStorage, VoicePostgresStoreOptions } from './postgresStore';
package/dist/index.js CHANGED
@@ -10096,6 +10096,46 @@ var createVoiceTraceSinkStore = (options) => {
10096
10096
  remove: (id) => options.store.remove(id)
10097
10097
  };
10098
10098
  };
10099
+ var normalizeVoiceProfileTraceTaggerProfile = (profile) => typeof profile === "string" ? { id: profile } : profile?.id ? profile : undefined;
10100
+ var createVoiceProfileTraceTagger = (options) => {
10101
+ const profiles = new Map((options.profiles ?? []).map((profile) => [profile.id, profile]));
10102
+ const defaultProfile = normalizeVoiceProfileTraceTaggerProfile(options.defaultProfile);
10103
+ const resolveProfile = async (event) => {
10104
+ const resolved = normalizeVoiceProfileTraceTaggerProfile(await options.resolveProfile?.(event));
10105
+ const profile = resolved ?? defaultProfile;
10106
+ return profile ? profiles.get(profile.id) ?? profile : undefined;
10107
+ };
10108
+ return {
10109
+ append: async (event) => {
10110
+ const profile = await resolveProfile(event);
10111
+ if (!profile) {
10112
+ return options.store.append(event);
10113
+ }
10114
+ const metadata = {
10115
+ ...event.metadata ?? {},
10116
+ benchmarkProfileId: profile.id,
10117
+ profileDescription: event.metadata?.profileDescription ?? profile.description,
10118
+ profileId: profile.id,
10119
+ profileLabel: event.metadata?.profileLabel ?? profile.label
10120
+ };
10121
+ const payload = event.payload && typeof event.payload === "object" ? {
10122
+ ...event.payload,
10123
+ benchmarkProfileId: event.payload.benchmarkProfileId ?? profile.id,
10124
+ profileDescription: event.payload.profileDescription ?? profile.description,
10125
+ profileId: event.payload.profileId ?? profile.id,
10126
+ profileLabel: event.payload.profileLabel ?? profile.label
10127
+ } : event.payload;
10128
+ return options.store.append({
10129
+ ...event,
10130
+ metadata,
10131
+ payload
10132
+ });
10133
+ },
10134
+ get: (id) => options.store.get(id),
10135
+ list: (filter) => options.store.list(filter),
10136
+ remove: (id) => options.store.remove(id)
10137
+ };
10138
+ };
10099
10139
  var createVoiceMemoryTraceSinkDeliveryStore = () => {
10100
10140
  const deliveries = new Map;
10101
10141
  return {
@@ -15445,6 +15485,20 @@ var maxNumber = (values) => {
15445
15485
  const finite = values.filter((value) => typeof value === "number" && Number.isFinite(value));
15446
15486
  return finite.length > 0 ? Math.max(...finite) : undefined;
15447
15487
  };
15488
+ var percentile2 = (values, rank) => {
15489
+ const finite = values.filter((value) => Number.isFinite(value)).sort((left, right) => left - right);
15490
+ if (finite.length === 0) {
15491
+ return;
15492
+ }
15493
+ const index = Math.min(finite.length - 1, Math.max(0, Math.ceil(rank / 100 * finite.length) - 1));
15494
+ return finite[index];
15495
+ };
15496
+ var averageNumber = (values) => {
15497
+ const finite = values.filter((value) => Number.isFinite(value));
15498
+ return finite.length === 0 ? undefined : Math.round(finite.reduce((total, value) => total + value, 0) / finite.length);
15499
+ };
15500
+ var readString2 = (value) => typeof value === "string" && value.trim() ? value : undefined;
15501
+ var readNumber2 = (value) => typeof value === "number" && Number.isFinite(value) ? value : undefined;
15448
15502
  var readProofTrendMaxLiveP95 = (report) => report.summary.maxLiveP95Ms ?? maxNumber(report.cycles.map((cycle) => cycle.liveLatency?.p95Ms));
15449
15503
  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
15504
  var readProofTrendMaxTurnP95 = (report) => report.summary.maxTurnP95Ms ?? maxNumber(report.cycles.map((cycle) => cycle.turnLatency?.p95Ms));
@@ -15508,6 +15562,149 @@ var aggregateProofTrendRuntimeChannel = (channels) => {
15508
15562
  status: channels.some((channel) => channel.status === "fail") ? "fail" : channels.some((channel) => channel.status === "warn") ? "warn" : channels.every((channel) => channel.status === "pass") ? "pass" : undefined
15509
15563
  };
15510
15564
  };
15565
+ var readTraceRecord = (event) => event.payload;
15566
+ var readTraceProfileId = (events, options) => {
15567
+ for (const event of events) {
15568
+ const payload = readTraceRecord(event);
15569
+ const profileId = readString2(payload.profileId) ?? readString2(event.metadata?.profileId) ?? readString2(payload.benchmarkProfileId) ?? readString2(event.metadata?.benchmarkProfileId);
15570
+ if (profileId) {
15571
+ return profileId;
15572
+ }
15573
+ }
15574
+ return options.defaultProfileId;
15575
+ };
15576
+ var readProviderTraceRole = (payload) => readString2(payload.kind) ?? readString2(payload.role) ?? readString2(payload.surface) ?? "provider";
15577
+ var readProviderTraceLatency = (payload) => readNumber2(payload.elapsedMs) ?? readNumber2(payload.latencyMs) ?? readNumber2(payload.durationMs);
15578
+ var readProviderTraceId = (payload) => readString2(payload.selectedProvider) ?? readString2(payload.provider) ?? readString2(payload.model) ?? readString2(payload.adapter);
15579
+ var readTraceStatus = (payload) => readString2(payload.providerStatus) ?? readString2(payload.status);
15580
+ var isFailingTraceStatus = (status) => status === "error" || status === "fail" || status === "failed" || status === "timeout";
15581
+ var summarizeProviderTraceEvidence = (events, maxProviderP95Ms) => {
15582
+ const providerLatencies = new Map;
15583
+ const providerMeta = new Map;
15584
+ for (const event of events) {
15585
+ if (event.type !== "session.error" && event.type !== "provider.decision") {
15586
+ continue;
15587
+ }
15588
+ const payload = readTraceRecord(event);
15589
+ const provider = readProviderTraceId(payload);
15590
+ if (!provider) {
15591
+ continue;
15592
+ }
15593
+ const role = readProviderTraceRole(payload);
15594
+ const id = `${role}:${provider}`;
15595
+ const latency = readProviderTraceLatency(payload);
15596
+ if (latency !== undefined) {
15597
+ providerLatencies.set(id, [...providerLatencies.get(id) ?? [], latency]);
15598
+ }
15599
+ const existing = providerMeta.get(id);
15600
+ providerMeta.set(id, {
15601
+ failed: existing?.failed === true || isFailingTraceStatus(readTraceStatus(payload)),
15602
+ label: existing?.label ?? `${role.toUpperCase()} ${provider}`,
15603
+ role: existing?.role ?? role
15604
+ });
15605
+ }
15606
+ return [...providerMeta.entries()].map(([id, meta]) => {
15607
+ const latencies = providerLatencies.get(id) ?? [];
15608
+ const p95Ms = percentile2(latencies, 95);
15609
+ return {
15610
+ averageMs: averageNumber(latencies),
15611
+ id,
15612
+ label: meta.label,
15613
+ p50Ms: percentile2(latencies, 50),
15614
+ p95Ms,
15615
+ role: meta.role,
15616
+ samples: latencies.length,
15617
+ status: meta.failed || (p95Ms ?? 0) > (maxProviderP95Ms ?? Number.POSITIVE_INFINITY) ? "fail" : latencies.length > 0 ? "pass" : "warn"
15618
+ };
15619
+ });
15620
+ };
15621
+ var summarizeTurnTraceP95 = (events) => {
15622
+ const explicit = events.filter((event) => event.type === "turn_latency.stage").map((event) => {
15623
+ const payload = readTraceRecord(event);
15624
+ return readNumber2(payload.totalMs) ?? readNumber2(payload.elapsedMs) ?? readNumber2(payload.latencyMs);
15625
+ }).filter((value) => value !== undefined);
15626
+ if (explicit.length > 0) {
15627
+ return percentile2(explicit, 95);
15628
+ }
15629
+ const turnStages = new Map;
15630
+ for (const event of events) {
15631
+ if (event.type !== "turn_latency.stage" || !event.turnId) {
15632
+ continue;
15633
+ }
15634
+ const key = `${event.sessionId}:${event.turnId}`;
15635
+ turnStages.set(key, [...turnStages.get(key) ?? [], event.at]);
15636
+ }
15637
+ const totals = [...turnStages.values()].map((stages) => stages.length < 2 ? undefined : Math.max(...stages) - Math.min(...stages)).filter((value) => value !== undefined);
15638
+ return percentile2(totals, 95);
15639
+ };
15640
+ var summarizeRuntimeChannelTraceEvidence = (events) => {
15641
+ const runtimeEvents = events.filter((event) => event.type === "client.browser_media" || event.type === "client.telephony_media" || event.type === "client.barge_in");
15642
+ if (runtimeEvents.length === 0) {
15643
+ return;
15644
+ }
15645
+ const firstAudio = runtimeEvents.map((event) => readNumber2(readTraceRecord(event).firstAudioLatencyMs)).filter((value) => value !== undefined);
15646
+ const jitter = runtimeEvents.map((event) => readNumber2(readTraceRecord(event).jitterMs)).filter((value) => value !== undefined);
15647
+ const timestampDrift = runtimeEvents.map((event) => readNumber2(readTraceRecord(event).timestampDriftMs)).filter((value) => value !== undefined);
15648
+ const backpressure = runtimeEvents.map((event) => readNumber2(readTraceRecord(event).backpressureEvents)).filter((value) => value !== undefined);
15649
+ const interruptions = runtimeEvents.map((event) => {
15650
+ const payload = readTraceRecord(event);
15651
+ return readNumber2(payload.interruptionLatencyMs) ?? readNumber2(payload.interruptionMs) ?? readNumber2(payload.elapsedMs);
15652
+ }).filter((value) => value !== undefined);
15653
+ return {
15654
+ maxBackpressureEvents: maxNumber(backpressure),
15655
+ maxFirstAudioLatencyMs: maxNumber(firstAudio),
15656
+ maxInterruptionP95Ms: percentile2(interruptions, 95),
15657
+ maxJitterMs: maxNumber(jitter),
15658
+ maxTimestampDriftMs: maxNumber(timestampDrift),
15659
+ samples: runtimeEvents.length,
15660
+ status: runtimeEvents.some((event) => isFailingTraceStatus(readTraceStatus(readTraceRecord(event)))) ? "fail" : "pass"
15661
+ };
15662
+ };
15663
+ var buildVoiceRealCallProfileEvidenceFromTraceEvents = (events, options = {}) => {
15664
+ const sessionFilter = new Set(options.sessionIds ?? []);
15665
+ const eventsBySession = new Map;
15666
+ for (const event of events) {
15667
+ if (sessionFilter.size > 0 && !sessionFilter.has(event.sessionId)) {
15668
+ continue;
15669
+ }
15670
+ eventsBySession.set(event.sessionId, [
15671
+ ...eventsBySession.get(event.sessionId) ?? [],
15672
+ event
15673
+ ]);
15674
+ }
15675
+ return [...eventsBySession.entries()].map(([
15676
+ sessionId,
15677
+ sessionEvents
15678
+ ]) => {
15679
+ const profileId = readTraceProfileId(sessionEvents, options);
15680
+ if (!profileId) {
15681
+ return;
15682
+ }
15683
+ const providers = summarizeProviderTraceEvidence(sessionEvents, options.maxProviderP95Ms);
15684
+ const liveLatencies = sessionEvents.filter((event) => event.type === "client.live_latency").map((event) => {
15685
+ const payload = readTraceRecord(event);
15686
+ return readNumber2(payload.latencyMs) ?? readNumber2(payload.elapsedMs);
15687
+ }).filter((value) => value !== undefined);
15688
+ const turnP95Ms = summarizeTurnTraceP95(sessionEvents);
15689
+ const providerP95Ms = maxNumber(providers.map((provider) => provider.p95Ms));
15690
+ const runtimeChannel = summarizeRuntimeChannelTraceEvidence(sessionEvents);
15691
+ return {
15692
+ generatedAt: new Date(Math.max(...sessionEvents.map((event) => event.at))).toISOString(),
15693
+ liveP95Ms: percentile2(liveLatencies, 95),
15694
+ ok: providers.every((provider) => provider.status !== "fail") && (runtimeChannel?.status ?? "pass") !== "fail",
15695
+ operationsRecordHref: `/voice-operations/${sessionId}`,
15696
+ profileDescription: options.profileDescriptions?.[profileId],
15697
+ profileId,
15698
+ profileLabel: options.profileLabels?.[profileId] ?? (profileId === options.defaultProfileId ? options.defaultProfileLabel : undefined),
15699
+ providerP95Ms,
15700
+ providers,
15701
+ runtimeChannel,
15702
+ sessionId,
15703
+ turnP95Ms
15704
+ };
15705
+ }).filter((evidence) => evidence !== undefined);
15706
+ };
15707
+ var loadVoiceRealCallProfileEvidenceFromTraceStore = async (options) => buildVoiceRealCallProfileEvidenceFromTraceEvents(await options.store.list({ limit: options.limit ?? 5000 }), options);
15511
15708
  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
15709
  var exceedsProofTrendBudget = (value, budget) => value !== undefined && (!Number.isFinite(value) || value > budget);
15513
15710
  var readProofTrendProfileStatus = (profile, budgets) => {
@@ -17189,7 +17386,7 @@ var DEFAULT_WARN_RATIO = 0.8;
17189
17386
  var DEFAULT_MIN_PASSING_RUNS = 3;
17190
17387
  var roundMs = (value) => Math.max(1, Math.ceil(value));
17191
17388
  var finiteNumber = (value) => typeof value === "number" && Number.isFinite(value) && value >= 0;
17192
- var percentile2 = (values, rank) => {
17389
+ var percentile3 = (values, rank) => {
17193
17390
  if (values.length === 0) {
17194
17391
  return;
17195
17392
  }
@@ -17213,7 +17410,7 @@ var normalizeSample = (input) => {
17213
17410
  return input;
17214
17411
  };
17215
17412
  var createThreshold = (metric, values, options) => {
17216
- const baselineP95Ms = percentile2(values, 95);
17413
+ const baselineP95Ms = percentile3(values, 95);
17217
17414
  const maxObservedMs = values.length > 0 ? Math.max(...values) : undefined;
17218
17415
  const recommendedMs = baselineP95Ms === undefined ? undefined : roundMs(Math.max(options.minimumMs, baselineP95Ms * options.headroomMultiplier));
17219
17416
  const warnAfterMs = recommendedMs === undefined ? undefined : roundMs(Math.max(options.minimumMs, recommendedMs * options.warnRatio));
@@ -21729,7 +21926,7 @@ var createVoiceTurnLatencyRoutes = (options) => {
21729
21926
  // src/liveLatency.ts
21730
21927
  import { Elysia as Elysia37 } from "elysia";
21731
21928
  var escapeHtml38 = (value) => value.replaceAll("&", "&amp;").replaceAll("<", "&lt;").replaceAll(">", "&gt;").replaceAll('"', "&quot;").replaceAll("'", "&#39;");
21732
- var percentile3 = (values, percentileValue) => {
21929
+ var percentile4 = (values, percentileValue) => {
21733
21930
  if (values.length === 0) {
21734
21931
  return;
21735
21932
  }
@@ -21765,8 +21962,8 @@ var summarizeVoiceLiveLatency = async (options) => {
21765
21962
  averageLatencyMs: latencies.length > 0 ? Math.round(latencies.reduce((total, value) => total + value, 0) / latencies.length) : undefined,
21766
21963
  checkedAt: Date.now(),
21767
21964
  failed,
21768
- p50LatencyMs: percentile3(latencies, 50),
21769
- p95LatencyMs: percentile3(latencies, 95),
21965
+ p50LatencyMs: percentile4(latencies, 50),
21966
+ p95LatencyMs: percentile4(latencies, 95),
21770
21967
  recent,
21771
21968
  status: latencies.length === 0 ? "empty" : failed > 0 ? "fail" : warnings > 0 ? "warn" : "pass",
21772
21969
  total: latencies.length,
@@ -21842,7 +22039,7 @@ var TRACE_TYPES = [
21842
22039
  ];
21843
22040
  var getNumber8 = (value) => typeof value === "number" && Number.isFinite(value) ? value : undefined;
21844
22041
  var getString14 = (value) => typeof value === "string" && value.trim() ? value : undefined;
21845
- var percentile4 = (values, percentileValue) => {
22042
+ var percentile5 = (values, percentileValue) => {
21846
22043
  if (values.length === 0) {
21847
22044
  return;
21848
22045
  }
@@ -22050,8 +22247,8 @@ var summarizeStage = (stage, measurements, options) => {
22050
22247
  label: STAGE_LABELS[stage],
22051
22248
  maxMs: latencies.length > 0 ? Math.max(...latencies) : undefined,
22052
22249
  measurements: stageMeasurements,
22053
- p50Ms: percentile4(latencies, 50),
22054
- p95Ms: percentile4(latencies, 95),
22250
+ p50Ms: percentile5(latencies, 50),
22251
+ p95Ms: percentile5(latencies, 95),
22055
22252
  stage,
22056
22253
  status: stageMeasurements.length === 0 ? "empty" : failed > 0 ? "fail" : warnings > 0 ? "warn" : "pass",
22057
22254
  total: stageMeasurements.length,
@@ -27869,7 +28066,7 @@ var rate3 = (count, total) => count / Math.max(1, total);
27869
28066
  var uniqueSorted7 = (values) => [
27870
28067
  ...new Set(values.filter((value) => typeof value === "string"))
27871
28068
  ].sort();
27872
- var percentile5 = (values, rank) => {
28069
+ var percentile6 = (values, rank) => {
27873
28070
  if (values.length === 0) {
27874
28071
  return 0;
27875
28072
  }
@@ -27927,7 +28124,7 @@ var summarizeKind = (kind, events, thresholds, required) => {
27927
28124
  unit: "rate"
27928
28125
  }),
27929
28126
  p95ElapsedMs: createMetric2({
27930
- actual: percentile5(latencies, 95),
28127
+ actual: percentile6(latencies, 95),
27931
28128
  label: "P95 latency",
27932
28129
  threshold: thresholds.maxP95ElapsedMs,
27933
28130
  unit: "ms"
@@ -38256,6 +38453,7 @@ export {
38256
38453
  muteVoiceMonitorIssue,
38257
38454
  matchesVoiceOpsTaskAssignmentRule,
38258
38455
  markVoiceOpsTaskSLABreached,
38456
+ loadVoiceRealCallProfileEvidenceFromTraceStore,
38259
38457
  loadVoiceObservabilityExportReplaySource,
38260
38458
  listVoiceRoutingEvents,
38261
38459
  listVoiceProviderDecisionTraces,
@@ -38439,6 +38637,7 @@ export {
38439
38637
  createVoiceProviderCapabilityHTMLHandler,
38440
38638
  createVoiceProofTrendRoutes,
38441
38639
  createVoiceProofTrendRecommendationRoutes,
38640
+ createVoiceProfileTraceTagger,
38442
38641
  createVoiceProfileSwitchReadinessRoutes,
38443
38642
  createVoiceProfileSwitchPolicyProofRoutes,
38444
38643
  createVoiceProfileSwitchLiveDecisionRoutes,
@@ -38634,6 +38833,7 @@ export {
38634
38833
  buildVoiceRealtimeChannelRuntimeSamplesFromTrace,
38635
38834
  buildVoiceRealtimeChannelReport,
38636
38835
  buildVoiceRealCallProfileHistoryReport,
38836
+ buildVoiceRealCallProfileEvidenceFromTraceEvents,
38637
38837
  buildVoiceRealCallProfileDefaults,
38638
38838
  buildVoiceProviderSloReport,
38639
38839
  buildVoiceProviderOrchestrationReport,
@@ -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;
@@ -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) => {
@@ -1359,6 +1359,46 @@ var createVoiceTraceSinkStore = (options) => {
1359
1359
  remove: (id) => options.store.remove(id)
1360
1360
  };
1361
1361
  };
1362
+ var normalizeVoiceProfileTraceTaggerProfile = (profile) => typeof profile === "string" ? { id: profile } : profile?.id ? profile : undefined;
1363
+ var createVoiceProfileTraceTagger = (options) => {
1364
+ const profiles = new Map((options.profiles ?? []).map((profile) => [profile.id, profile]));
1365
+ const defaultProfile = normalizeVoiceProfileTraceTaggerProfile(options.defaultProfile);
1366
+ const resolveProfile = async (event) => {
1367
+ const resolved = normalizeVoiceProfileTraceTaggerProfile(await options.resolveProfile?.(event));
1368
+ const profile = resolved ?? defaultProfile;
1369
+ return profile ? profiles.get(profile.id) ?? profile : undefined;
1370
+ };
1371
+ return {
1372
+ append: async (event) => {
1373
+ const profile = await resolveProfile(event);
1374
+ if (!profile) {
1375
+ return options.store.append(event);
1376
+ }
1377
+ const metadata = {
1378
+ ...event.metadata ?? {},
1379
+ benchmarkProfileId: profile.id,
1380
+ profileDescription: event.metadata?.profileDescription ?? profile.description,
1381
+ profileId: profile.id,
1382
+ profileLabel: event.metadata?.profileLabel ?? profile.label
1383
+ };
1384
+ const payload = event.payload && typeof event.payload === "object" ? {
1385
+ ...event.payload,
1386
+ benchmarkProfileId: event.payload.benchmarkProfileId ?? profile.id,
1387
+ profileDescription: event.payload.profileDescription ?? profile.description,
1388
+ profileId: event.payload.profileId ?? profile.id,
1389
+ profileLabel: event.payload.profileLabel ?? profile.label
1390
+ } : event.payload;
1391
+ return options.store.append({
1392
+ ...event,
1393
+ metadata,
1394
+ payload
1395
+ });
1396
+ },
1397
+ get: (id) => options.store.get(id),
1398
+ list: (filter) => options.store.list(filter),
1399
+ remove: (id) => options.store.remove(id)
1400
+ };
1401
+ };
1362
1402
  var createVoiceMemoryTraceSinkDeliveryStore = () => {
1363
1403
  const deliveries = new Map;
1364
1404
  return {
@@ -9524,6 +9524,46 @@ var createVoiceTraceSinkStore = (options) => {
9524
9524
  remove: (id) => options.store.remove(id)
9525
9525
  };
9526
9526
  };
9527
+ var normalizeVoiceProfileTraceTaggerProfile = (profile) => typeof profile === "string" ? { id: profile } : profile?.id ? profile : undefined;
9528
+ var createVoiceProfileTraceTagger = (options) => {
9529
+ const profiles = new Map((options.profiles ?? []).map((profile) => [profile.id, profile]));
9530
+ const defaultProfile = normalizeVoiceProfileTraceTaggerProfile(options.defaultProfile);
9531
+ const resolveProfile = async (event) => {
9532
+ const resolved = normalizeVoiceProfileTraceTaggerProfile(await options.resolveProfile?.(event));
9533
+ const profile = resolved ?? defaultProfile;
9534
+ return profile ? profiles.get(profile.id) ?? profile : undefined;
9535
+ };
9536
+ return {
9537
+ append: async (event) => {
9538
+ const profile = await resolveProfile(event);
9539
+ if (!profile) {
9540
+ return options.store.append(event);
9541
+ }
9542
+ const metadata = {
9543
+ ...event.metadata ?? {},
9544
+ benchmarkProfileId: profile.id,
9545
+ profileDescription: event.metadata?.profileDescription ?? profile.description,
9546
+ profileId: profile.id,
9547
+ profileLabel: event.metadata?.profileLabel ?? profile.label
9548
+ };
9549
+ const payload = event.payload && typeof event.payload === "object" ? {
9550
+ ...event.payload,
9551
+ benchmarkProfileId: event.payload.benchmarkProfileId ?? profile.id,
9552
+ profileDescription: event.payload.profileDescription ?? profile.description,
9553
+ profileId: event.payload.profileId ?? profile.id,
9554
+ profileLabel: event.payload.profileLabel ?? profile.label
9555
+ } : event.payload;
9556
+ return options.store.append({
9557
+ ...event,
9558
+ metadata,
9559
+ payload
9560
+ });
9561
+ },
9562
+ get: (id) => options.store.get(id),
9563
+ list: (filter) => options.store.list(filter),
9564
+ remove: (id) => options.store.remove(id)
9565
+ };
9566
+ };
9527
9567
  var createVoiceMemoryTraceSinkDeliveryStore = () => {
9528
9568
  const deliveries = new Map;
9529
9569
  return {
package/dist/trace.d.ts CHANGED
@@ -127,6 +127,17 @@ export type VoiceTraceSinkStoreOptions<TEvent extends StoredVoiceTraceEvent = St
127
127
  sinks: VoiceTraceSink[];
128
128
  store: VoiceTraceEventStore<TEvent>;
129
129
  };
130
+ export type VoiceProfileTraceTaggerProfile = {
131
+ description?: string;
132
+ id: string;
133
+ label?: string;
134
+ };
135
+ export type VoiceProfileTraceTaggerOptions<TEvent extends StoredVoiceTraceEvent = StoredVoiceTraceEvent> = {
136
+ defaultProfile?: VoiceProfileTraceTaggerProfile | string;
137
+ profiles?: readonly VoiceProfileTraceTaggerProfile[];
138
+ resolveProfile?: (event: VoiceTraceEvent | TEvent) => Promise<VoiceProfileTraceTaggerProfile | string | undefined> | VoiceProfileTraceTaggerProfile | string | undefined;
139
+ store: VoiceTraceEventStore<TEvent>;
140
+ };
130
141
  export type VoiceTraceSummary = {
131
142
  assistantReplyCount: number;
132
143
  callDurationMs?: number;
@@ -211,6 +222,7 @@ export declare const deliverVoiceTraceEventsToSinks: (input: {
211
222
  sinks: VoiceTraceSink[];
212
223
  }) => Promise<VoiceTraceSinkFanoutResult>;
213
224
  export declare const createVoiceTraceSinkStore: <TEvent extends StoredVoiceTraceEvent = StoredVoiceTraceEvent>(options: VoiceTraceSinkStoreOptions<TEvent>) => VoiceTraceEventStore<TEvent>;
225
+ export declare const createVoiceProfileTraceTagger: <TEvent extends StoredVoiceTraceEvent = StoredVoiceTraceEvent>(options: VoiceProfileTraceTaggerOptions<TEvent>) => VoiceTraceEventStore<TEvent>;
214
226
  export declare const createVoiceMemoryTraceSinkDeliveryStore: <TDelivery extends VoiceTraceSinkDeliveryRecord = VoiceTraceSinkDeliveryRecord>() => VoiceTraceSinkDeliveryStore<TDelivery>;
215
227
  export declare const createVoiceMemoryTraceEventStore: <TEvent extends StoredVoiceTraceEvent = StoredVoiceTraceEvent>() => VoiceTraceEventStore<TEvent>;
216
228
  export declare const exportVoiceTrace: (input: {
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) => {
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@absolutejs/voice",
3
- "version": "0.0.22-beta.367",
3
+ "version": "0.0.22-beta.369",
4
4
  "description": "Voice primitives and Elysia plugin for AbsoluteJS",
5
5
  "repository": {
6
6
  "type": "git",