@absolutejs/voice 0.0.22-beta.334 → 0.0.22-beta.336

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.
@@ -3821,7 +3821,7 @@ var maxNumber = (values) => {
3821
3821
  return finite.length > 0 ? Math.max(...finite) : undefined;
3822
3822
  };
3823
3823
  var readProofTrendMaxLiveP95 = (report) => report.summary.maxLiveP95Ms ?? maxNumber(report.cycles.map((cycle) => cycle.liveLatency?.p95Ms));
3824
- var readProofTrendMaxProviderP95 = (report) => report.summary.maxProviderP95Ms;
3824
+ 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)));
3825
3825
  var readProofTrendMaxTurnP95 = (report) => report.summary.maxTurnP95Ms ?? maxNumber(report.cycles.map((cycle) => cycle.turnLatency?.p95Ms));
3826
3826
  var readRuntimeChannelMetric = (report, key) => {
3827
3827
  const summaryValue = report.summary.runtimeChannel?.[key];
@@ -3839,6 +3839,103 @@ var readProofTrendRuntimeChannel = (report) => ({
3839
3839
  samples: report.summary.runtimeChannel?.samples ?? maxNumber(report.cycles.map((cycle) => cycle.runtimeChannel?.samples)),
3840
3840
  status: report.summary.runtimeChannel?.status
3841
3841
  });
3842
+ var normalizeProviderStatus = (status) => status === "pass" ? "pass" : status === "fail" ? "fail" : "warn";
3843
+ var providerSortScore = (provider) => [
3844
+ recommendationStatusRank[provider.status],
3845
+ provider.p95Ms ?? Number.POSITIVE_INFINITY,
3846
+ provider.averageMs ?? Number.POSITIVE_INFINITY,
3847
+ provider.samples === undefined ? Number.POSITIVE_INFINITY : -provider.samples,
3848
+ provider.id
3849
+ ];
3850
+ var compareProviders = (left, right) => {
3851
+ const leftScore = providerSortScore(left);
3852
+ const rightScore = providerSortScore(right);
3853
+ for (let index = 0;index < leftScore.length; index += 1) {
3854
+ const leftValue = leftScore[index];
3855
+ const rightValue = rightScore[index];
3856
+ if (typeof leftValue === "number" && typeof rightValue === "number") {
3857
+ if (leftValue !== rightValue) {
3858
+ return leftValue - rightValue;
3859
+ }
3860
+ continue;
3861
+ }
3862
+ const compared = String(leftValue).localeCompare(String(rightValue));
3863
+ if (compared !== 0) {
3864
+ return compared;
3865
+ }
3866
+ }
3867
+ return 0;
3868
+ };
3869
+ var summarizeProofTrendProviders = (report, budgetMs) => {
3870
+ const sourceProviders = report.summary.providers && report.summary.providers.length > 0 ? report.summary.providers : undefined;
3871
+ const providersById = new Map;
3872
+ if (sourceProviders) {
3873
+ for (const provider of sourceProviders) {
3874
+ if (provider.id) {
3875
+ providersById.set(provider.id, provider);
3876
+ }
3877
+ }
3878
+ } else {
3879
+ for (const cycle of report.cycles) {
3880
+ for (const provider of cycle.providers ?? []) {
3881
+ if (!provider.id) {
3882
+ continue;
3883
+ }
3884
+ const existing = providersById.get(provider.id);
3885
+ providersById.set(provider.id, {
3886
+ averageMs: maxNumber([existing?.averageMs, provider.averageMs]),
3887
+ id: provider.id,
3888
+ label: existing?.label ?? provider.label,
3889
+ p50Ms: maxNumber([existing?.p50Ms, provider.p50Ms]),
3890
+ p95Ms: maxNumber([existing?.p95Ms, provider.p95Ms]),
3891
+ role: existing?.role ?? provider.role,
3892
+ samples: (existing?.samples ?? 0) + (provider.samples ?? 0),
3893
+ status: existing?.status === "fail" || provider.status === "fail" ? "fail" : existing?.status === "warn" || provider.status === "warn" ? "warn" : provider.status ?? existing?.status
3894
+ });
3895
+ }
3896
+ }
3897
+ }
3898
+ return [...providersById.values()].map((provider) => {
3899
+ const status = provider.p95Ms === undefined ? normalizeProviderStatus(provider.status) : withinBudget(provider.p95Ms, budgetMs) ? normalizeProviderStatus(provider.status) === "fail" ? "fail" : "pass" : normalizeProviderStatus(provider.status) === "fail" ? "fail" : "warn";
3900
+ return {
3901
+ averageMs: provider.averageMs,
3902
+ id: provider.id,
3903
+ label: provider.label,
3904
+ nextMove: status === "pass" ? "Eligible for latency-sensitive routing based on sustained proof." : provider.p95Ms === undefined ? "Collect provider-specific latency samples before routing latency-sensitive traffic here." : "Keep as fallback or tune provider/model/runtime budgets before using for latency-sensitive routing.",
3905
+ p50Ms: provider.p50Ms,
3906
+ p95Ms: provider.p95Ms,
3907
+ rank: 0,
3908
+ role: provider.role,
3909
+ samples: provider.samples,
3910
+ status
3911
+ };
3912
+ }).sort(compareProviders).map((provider, index) => ({ ...provider, rank: index + 1 }));
3913
+ };
3914
+ var shouldSwitchProvider = (current, best, options) => {
3915
+ if (!current || !best || current.id === best.id || best.status !== "pass") {
3916
+ return false;
3917
+ }
3918
+ if (current.p95Ms === undefined || best.p95Ms === undefined) {
3919
+ return false;
3920
+ }
3921
+ const minImprovementMs = options.providerSwitchMinImprovementMs ?? 100;
3922
+ const minImprovementRatio = options.providerSwitchMinImprovementRatio ?? 0.1;
3923
+ const improvementMs = current.p95Ms - best.p95Ms;
3924
+ const improvementRatio = current.p95Ms > 0 ? improvementMs / current.p95Ms : 0;
3925
+ return improvementMs >= minImprovementMs || improvementRatio >= minImprovementRatio;
3926
+ };
3927
+ var bestProviderByRole = (providers) => {
3928
+ const best = new Map;
3929
+ for (const provider of providers) {
3930
+ const role = provider.role ?? provider.id;
3931
+ const existing = best.get(role);
3932
+ if (!existing || compareProviders(provider, existing) < 0) {
3933
+ best.set(role, provider);
3934
+ }
3935
+ }
3936
+ return [...best.values()].sort((left, right) => String(left.role ?? left.id).localeCompare(String(right.role ?? right.id)));
3937
+ };
3938
+ var formatProviderMix = (providers) => providers.length === 0 ? "n/a" : providers.map((provider) => provider.role ? `${provider.role.toUpperCase()} ${provider.label ?? provider.id}` : provider.label ?? provider.id).join(", ");
3842
3939
  var evaluateVoiceProofTrendEvidence = (report, input = {}) => {
3843
3940
  const issues = [];
3844
3941
  const requiredStatus = input.requireStatus ?? "pass";
@@ -3953,6 +4050,12 @@ var buildVoiceProofTrendRecommendationReport = (report, options = {}) => {
3953
4050
  const maxProviderP95Ms = readProofTrendMaxProviderP95(report);
3954
4051
  const maxTurnP95Ms = readProofTrendMaxTurnP95(report);
3955
4052
  const runtimeChannel = readProofTrendRuntimeChannel(report);
4053
+ const providers = summarizeProofTrendProviders(report, budgets.maxProviderP95Ms);
4054
+ const bestProvider = providers.find((provider) => provider.status === "pass") ?? providers[0];
4055
+ const bestProviders = bestProviderByRole(providers).filter((provider) => provider.status === "pass");
4056
+ const currentProvider = options.currentProviderId ? providers.find((provider) => provider.id === options.currentProviderId) : undefined;
4057
+ const bestComparableProvider = currentProvider?.role ? bestProviders.find((provider) => provider.role === currentProvider.role) : bestProvider;
4058
+ const providerSwitchRecommended = shouldSwitchProvider(currentProvider, bestComparableProvider, options);
3956
4059
  const recommendations = [];
3957
4060
  const issues = [];
3958
4061
  if (report.ok !== true) {
@@ -3960,12 +4063,20 @@ var buildVoiceProofTrendRecommendationReport = (report, options = {}) => {
3960
4063
  }
3961
4064
  recommendations.push({
3962
4065
  evidence: {
4066
+ bestProviderId: bestComparableProvider?.id ?? bestProvider?.id,
4067
+ bestProviderMix: formatProviderMix(bestProviders),
4068
+ bestProviderP95Ms: bestComparableProvider?.p95Ms ?? bestProvider?.p95Ms,
3963
4069
  budgetMs: budgets.maxProviderP95Ms,
4070
+ currentProviderId: currentProvider?.id ?? options.currentProviderId,
4071
+ currentProviderP95Ms: currentProvider?.p95Ms,
4072
+ providerComparisonCount: providers.length,
3964
4073
  providerP95Ms: maxProviderP95Ms
3965
4074
  },
3966
- nextMove: withinBudget(maxProviderP95Ms, budgets.maxProviderP95Ms) ? "Keep the current provider route for latency-sensitive turns and keep collecting sustained proof." : "Route latency-sensitive turns to a faster provider profile or tighten fallback/circuit-breaker budgets before promotion.",
3967
- recommendation: withinBudget(maxProviderP95Ms, budgets.maxProviderP95Ms) ? "Keep current provider path" : "Change provider routing for latency-sensitive traffic",
3968
- status: withinBudget(maxProviderP95Ms, budgets.maxProviderP95Ms) ? "pass" : maxProviderP95Ms === undefined ? "fail" : "warn",
4075
+ nextMove: providers.length > 0 ? providerSwitchRecommended ? `Route latency-sensitive ${currentProvider?.role ?? "provider"} traffic to ${bestComparableProvider?.label ?? bestComparableProvider?.id} for this call profile and keep the current path as fallback.` : bestProviders.length > 0 ? `Use the fastest proven provider mix for this call profile: ${formatProviderMix(bestProviders)}.` : "Collect provider-specific sustained samples before making provider-specific routing decisions." : withinBudget(maxProviderP95Ms, budgets.maxProviderP95Ms) ? "Keep the current provider route for latency-sensitive turns and keep collecting sustained proof." : "Route latency-sensitive turns to a faster provider profile or tighten fallback/circuit-breaker budgets before promotion.",
4076
+ providerId: providerSwitchRecommended ? bestComparableProvider?.id : bestProviders.length === 1 ? bestProviders[0]?.id : undefined,
4077
+ recommendation: providers.length > 0 ? providerSwitchRecommended ? `Switch latency-sensitive ${currentProvider?.role ?? "provider"} routing to ${bestComparableProvider?.label ?? bestComparableProvider?.id}` : bestProviders.length > 0 ? "Prefer the fastest proven provider mix for this call profile" : "Collect provider-specific latency samples" : withinBudget(maxProviderP95Ms, budgets.maxProviderP95Ms) ? "Keep current provider path" : "Change provider routing for latency-sensitive traffic",
4078
+ role: bestComparableProvider?.role,
4079
+ status: providers.length > 0 ? providerSwitchRecommended ? "warn" : bestProviders.length > 0 ? "pass" : bestProvider?.status ?? "fail" : withinBudget(maxProviderP95Ms, budgets.maxProviderP95Ms) ? "pass" : maxProviderP95Ms === undefined ? "fail" : "warn",
3969
4080
  surface: "provider-path"
3970
4081
  });
3971
4082
  const runtimePass = withinBudget(runtimeChannel.maxFirstAudioLatencyMs, budgets.maxRuntimeFirstAudioLatencyMs) && withinBudget(runtimeChannel.maxInterruptionP95Ms, budgets.maxRuntimeInterruptionP95Ms) && withinBudget(runtimeChannel.maxJitterMs, budgets.maxRuntimeJitterMs) && withinBudget(runtimeChannel.maxTimestampDriftMs, budgets.maxRuntimeTimestampDriftMs) && withinBudget(runtimeChannel.maxBackpressureEvents, budgets.maxRuntimeBackpressureEvents);
@@ -4008,16 +4119,21 @@ var buildVoiceProofTrendRecommendationReport = (report, options = {}) => {
4008
4119
  });
4009
4120
  const status = issues.length > 0 ? "fail" : worstRecommendationStatus(recommendations);
4010
4121
  return {
4122
+ bestProvider,
4123
+ bestProviders,
4011
4124
  generatedAt: new Date().toISOString(),
4012
4125
  issues,
4013
4126
  ok: status !== "fail",
4127
+ providers,
4014
4128
  recommendations,
4015
4129
  source: report.source || report.outputDir || report.runId || "proof-trends",
4016
4130
  status,
4017
4131
  summary: {
4018
- keepCurrentProviderPath: recommendations.find((item) => item.surface === "provider-path")?.status === "pass",
4132
+ keepCurrentProviderPath: !providerSwitchRecommended && recommendations.find((item) => item.surface === "provider-path")?.status !== "fail",
4019
4133
  keepCurrentRuntimeChannel: recommendations.find((item) => item.surface === "runtime-channel")?.status === "pass",
4020
- recommendedActions: recommendations.filter((item) => item.status !== "pass").length
4134
+ providerComparisonCount: providers.length,
4135
+ recommendedActions: recommendations.filter((item) => item.status !== "pass").length,
4136
+ switchRecommended: providerSwitchRecommended
4021
4137
  }
4022
4138
  };
4023
4139
  };
@@ -4028,12 +4144,20 @@ var renderVoiceProofTrendRecommendationMarkdown = (report, title = "Voice Provid
4028
4144
  "",
4029
4145
  `- Status: ${report.status}`,
4030
4146
  `- Source: ${report.source}`,
4147
+ `- Best provider mix: ${formatProviderMix(report.bestProviders)}`,
4148
+ `- Provider comparisons: ${String(report.summary.providerComparisonCount)}`,
4031
4149
  `- Recommended actions: ${String(report.summary.recommendedActions)}`,
4032
4150
  "",
4033
4151
  "| Surface | Status | Recommendation | Next move |",
4034
4152
  "| --- | --- | --- | --- |",
4035
4153
  ...report.recommendations.map((recommendation) => `| ${escapeMarkdown(recommendation.surface)} | ${recommendation.status} | ${escapeMarkdown(recommendation.recommendation)} | ${escapeMarkdown(recommendation.nextMove)} |`),
4036
4154
  "",
4155
+ "## Provider Comparison",
4156
+ "",
4157
+ "| Rank | Provider | Role | Status | P95 | Samples | Next move |",
4158
+ "| ---: | --- | --- | --- | ---: | ---: | --- |",
4159
+ ...report.providers.length ? report.providers.map((provider) => `| ${String(provider.rank)} | ${escapeMarkdown(provider.label ?? provider.id)} | ${escapeMarkdown(provider.role ?? "n/a")} | ${provider.status} | ${provider.p95Ms === undefined ? "n/a" : String(provider.p95Ms)} | ${provider.samples === undefined ? "n/a" : String(provider.samples)} | ${escapeMarkdown(provider.nextMove)} |`) : ["| n/a | n/a | n/a | n/a | n/a | n/a | No provider-specific samples were present. |"],
4160
+ "",
4037
4161
  "## Issues",
4038
4162
  "",
4039
4163
  ...report.issues.length ? report.issues.map((issue) => `- ${issue}`) : ["- None"]
@@ -4042,7 +4166,8 @@ var renderVoiceProofTrendRecommendationMarkdown = (report, title = "Voice Provid
4042
4166
  var renderVoiceProofTrendRecommendationHTML = (report, title = "Voice Provider Runtime Recommendations") => {
4043
4167
  const cards = report.recommendations.map((recommendation) => `<article class="${escapeHtml3(recommendation.status)}"><p class="eyebrow">${escapeHtml3(recommendation.surface)} \xB7 ${escapeHtml3(recommendation.status)}</p><h2>${escapeHtml3(recommendation.recommendation)}</h2><p>${escapeHtml3(recommendation.nextMove)}</p><pre>${escapeHtml3(JSON.stringify(recommendation.evidence, null, 2))}</pre></article>`).join("");
4044
4168
  const issues = report.issues.length === 0 ? "<li>None</li>" : report.issues.map((issue) => `<li>${escapeHtml3(issue)}</li>`).join("");
4045
- return `<!doctype html><html lang="en"><head><meta charset="utf-8" /><meta name="viewport" content="width=device-width,initial-scale=1" /><title>${escapeHtml3(title)}</title><style>body{background:#101418;color:#f7f3e8;font-family:ui-sans-serif,system-ui,sans-serif;margin:0}main{margin:auto;max-width:1120px;padding:32px}.hero,article{background:#17201d;border:1px solid #2e3d36;border-radius:24px;margin-bottom:16px;padding:22px}.hero{background:linear-gradient(135deg,rgba(20,184,166,.18),rgba(245,158,11,.12))}.eyebrow{color:#5eead4;font-weight:900;letter-spacing:.1em;text-transform:uppercase}h1{font-size:clamp(2.2rem,6vw,4.7rem);letter-spacing:-.06em;line-height:.92;margin:.2rem 0 1rem}.summary{display:flex;flex-wrap:wrap;gap:10px}.pill{border:1px solid #42534a;border-radius:999px;padding:8px 12px}.pass{border-color:rgba(34,197,94,.55)}.warn{border-color:rgba(245,158,11,.7)}.fail{border-color:rgba(239,68,68,.75)}pre{background:#0b1110;border-radius:14px;overflow:auto;padding:12px}a{color:#5eead4}</style></head><body><main><section class="hero"><p class="eyebrow">Sustained proof recommendations</p><h1>${escapeHtml3(title)}</h1><p>Generated ${escapeHtml3(report.generatedAt)} from ${escapeHtml3(report.source)}.</p><div class="summary"><span class="pill">Status ${escapeHtml3(report.status)}</span><span class="pill">Provider ${report.summary.keepCurrentProviderPath ? "keep" : "change"}</span><span class="pill">Runtime ${report.summary.keepCurrentRuntimeChannel ? "keep" : "tune"}</span><span class="pill">${String(report.summary.recommendedActions)} action(s)</span></div></section>${cards}<section class="hero"><h2>Issues</h2><ul>${issues}</ul></section></main></body></html>`;
4169
+ const providerRows = report.providers.length === 0 ? "<li>No provider-specific samples were present.</li>" : report.providers.map((provider) => `<li><strong>#${String(provider.rank)} ${escapeHtml3(provider.label ?? provider.id)}</strong><span>${escapeHtml3(provider.role ?? "provider")} \xB7 ${escapeHtml3(provider.status)} \xB7 p95 ${escapeHtml3(provider.p95Ms ?? "n/a")}ms \xB7 ${escapeHtml3(provider.samples ?? "n/a")} sample(s)</span><small>${escapeHtml3(provider.nextMove)}</small></li>`).join("");
4170
+ return `<!doctype html><html lang="en"><head><meta charset="utf-8" /><meta name="viewport" content="width=device-width,initial-scale=1" /><title>${escapeHtml3(title)}</title><style>body{background:#101418;color:#f7f3e8;font-family:ui-sans-serif,system-ui,sans-serif;margin:0}main{margin:auto;max-width:1120px;padding:32px}.hero,article{background:#17201d;border:1px solid #2e3d36;border-radius:24px;margin-bottom:16px;padding:22px}.hero{background:linear-gradient(135deg,rgba(20,184,166,.18),rgba(245,158,11,.12))}.eyebrow{color:#5eead4;font-weight:900;letter-spacing:.1em;text-transform:uppercase}h1{font-size:clamp(2.2rem,6vw,4.7rem);letter-spacing:-.06em;line-height:.92;margin:.2rem 0 1rem}.summary{display:flex;flex-wrap:wrap;gap:10px}.pill{border:1px solid #42534a;border-radius:999px;padding:8px 12px}.pass{border-color:rgba(34,197,94,.55)}.warn{border-color:rgba(245,158,11,.7)}.fail{border-color:rgba(239,68,68,.75)}pre{background:#0b1110;border-radius:14px;overflow:auto;padding:12px}a{color:#5eead4}li{margin:.45rem 0}li span,li small{display:block;color:#c9d3ca}</style></head><body><main><section class="hero"><p class="eyebrow">Sustained proof recommendations</p><h1>${escapeHtml3(title)}</h1><p>Generated ${escapeHtml3(report.generatedAt)} from ${escapeHtml3(report.source)}.</p><div class="summary"><span class="pill">Status ${escapeHtml3(report.status)}</span><span class="pill">Provider ${report.summary.keepCurrentProviderPath ? "keep" : "change"}</span><span class="pill">Best mix ${escapeHtml3(formatProviderMix(report.bestProviders))}</span><span class="pill">Runtime ${report.summary.keepCurrentRuntimeChannel ? "keep" : "tune"}</span><span class="pill">${String(report.summary.recommendedActions)} action(s)</span></div></section>${cards}<section class="hero"><h2>Provider Comparison</h2><ul>${providerRows}</ul></section><section class="hero"><h2>Issues</h2><ul>${issues}</ul></section></main></body></html>`;
4046
4171
  };
4047
4172
  var createVoiceProofTrendRecommendationRoutes = (options) => {
4048
4173
  const path = options.path ?? "/api/voice/proof-trend-recommendations";
package/dist/index.d.ts CHANGED
@@ -31,7 +31,7 @@ export type { VoicePlatformCoverageAssertionInput, VoicePlatformCoverageAssertio
31
31
  export { assertVoiceProofTrendEvidence, buildEmptyVoiceProofTrendReport, buildVoiceProofTrendRecommendationReport, buildVoiceProofTrendReport, createVoiceProofTrendRecommendationRoutes, createVoiceProofTrendRoutes, DEFAULT_VOICE_PROOF_TRENDS_MAX_AGE_MS, evaluateVoiceProofTrendEvidence, formatVoiceProofTrendAge, normalizeVoiceProofTrendReport, readVoiceProofTrendReportFile, renderVoiceProofTrendRecommendationHTML, renderVoiceProofTrendRecommendationMarkdown } from './proofTrends';
32
32
  export { buildVoiceProviderDecisionTraceReport, createVoiceProviderDecisionTraceEvent, createVoiceProviderDecisionTraceRoutes, listVoiceProviderDecisionTraces, renderVoiceProviderDecisionTraceHTML, renderVoiceProviderDecisionTraceMarkdown } from './providerDecisionTraces';
33
33
  export type { VoiceProviderDecisionStatus, VoiceProviderDecisionSurfaceReport, VoiceProviderDecisionTrace, VoiceProviderDecisionTraceInput, VoiceProviderDecisionTraceIssue, VoiceProviderDecisionTraceReport, VoiceProviderDecisionTraceReportOptions, VoiceProviderDecisionTraceRoutesOptions } from './providerDecisionTraces';
34
- export type { VoiceProofTrendAssertionInput, VoiceProofTrendAssertionReport, VoiceProofTrendCycle, VoiceProofTrendRecommendation, VoiceProofTrendRecommendationOptions, VoiceProofTrendRecommendationReport, VoiceProofTrendRecommendationRoutesOptions, VoiceProofTrendRecommendationStatus, VoiceProofTrendRecommendationSurface, VoiceProofTrendReport, VoiceProofTrendReportInput, VoiceProofTrendRoutesOptions, VoiceProofTrendStatus, VoiceProofTrendSummary } from './proofTrends';
34
+ export type { VoiceProofTrendAssertionInput, VoiceProofTrendAssertionReport, VoiceProofTrendCycle, VoiceProofTrendProviderRecommendation, VoiceProofTrendProviderSummary, VoiceProofTrendRecommendation, VoiceProofTrendRecommendationOptions, VoiceProofTrendRecommendationReport, VoiceProofTrendRecommendationRoutesOptions, VoiceProofTrendRecommendationStatus, VoiceProofTrendRecommendationSurface, VoiceProofTrendReport, VoiceProofTrendReportInput, VoiceProofTrendRoutesOptions, VoiceProofTrendStatus, VoiceProofTrendSummary } from './proofTrends';
35
35
  export { assertVoiceSloCalibration, buildVoiceSloCalibrationReport, buildVoiceSloReadinessThresholdReport, createVoiceSloReadinessThresholdOptions, createVoiceSloReadinessThresholdRoutes, createVoiceSloThresholdProfile, createVoiceSloCalibrationRoutes, renderVoiceSloCalibrationMarkdown, renderVoiceSloReadinessThresholdHTML, renderVoiceSloReadinessThresholdMarkdown } from './sloCalibration';
36
36
  export type { VoiceSloCalibrationMetricKey, VoiceSloCalibrationOptions, VoiceSloCalibrationReport, VoiceSloCalibrationRoutesOptions, VoiceSloCalibrationSample, VoiceSloCalibrationStatus, VoiceSloCalibrationThreshold, VoiceSloCalibrationThresholds, VoiceSloReadinessThresholdReport, VoiceSloReadinessThresholdReportOptions, VoiceSloReadinessThresholdOptions, VoiceSloReadinessThresholdRoutesOptions, VoiceSloThresholdProfile } from './sloCalibration';
37
37
  export { assertVoiceLiveOpsControlEvidence, assertVoiceLiveOpsEvidence, buildVoiceLiveOpsControlState, createVoiceLiveOpsController, createVoiceLiveOpsRoutes, createVoiceMemoryLiveOpsControlStore, evaluateVoiceLiveOpsControlEvidence, evaluateVoiceLiveOpsEvidence, getVoiceLiveOpsControlStatus, VOICE_LIVE_OPS_ACTIONS } from './liveOps';
package/dist/index.js CHANGED
@@ -14538,7 +14538,7 @@ var maxNumber = (values) => {
14538
14538
  return finite.length > 0 ? Math.max(...finite) : undefined;
14539
14539
  };
14540
14540
  var readProofTrendMaxLiveP95 = (report) => report.summary.maxLiveP95Ms ?? maxNumber(report.cycles.map((cycle) => cycle.liveLatency?.p95Ms));
14541
- var readProofTrendMaxProviderP95 = (report) => report.summary.maxProviderP95Ms;
14541
+ 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)));
14542
14542
  var readProofTrendMaxTurnP95 = (report) => report.summary.maxTurnP95Ms ?? maxNumber(report.cycles.map((cycle) => cycle.turnLatency?.p95Ms));
14543
14543
  var readRuntimeChannelMetric = (report, key) => {
14544
14544
  const summaryValue = report.summary.runtimeChannel?.[key];
@@ -14556,6 +14556,103 @@ var readProofTrendRuntimeChannel = (report) => ({
14556
14556
  samples: report.summary.runtimeChannel?.samples ?? maxNumber(report.cycles.map((cycle) => cycle.runtimeChannel?.samples)),
14557
14557
  status: report.summary.runtimeChannel?.status
14558
14558
  });
14559
+ var normalizeProviderStatus = (status) => status === "pass" ? "pass" : status === "fail" ? "fail" : "warn";
14560
+ var providerSortScore = (provider) => [
14561
+ recommendationStatusRank[provider.status],
14562
+ provider.p95Ms ?? Number.POSITIVE_INFINITY,
14563
+ provider.averageMs ?? Number.POSITIVE_INFINITY,
14564
+ provider.samples === undefined ? Number.POSITIVE_INFINITY : -provider.samples,
14565
+ provider.id
14566
+ ];
14567
+ var compareProviders = (left, right) => {
14568
+ const leftScore = providerSortScore(left);
14569
+ const rightScore = providerSortScore(right);
14570
+ for (let index = 0;index < leftScore.length; index += 1) {
14571
+ const leftValue = leftScore[index];
14572
+ const rightValue = rightScore[index];
14573
+ if (typeof leftValue === "number" && typeof rightValue === "number") {
14574
+ if (leftValue !== rightValue) {
14575
+ return leftValue - rightValue;
14576
+ }
14577
+ continue;
14578
+ }
14579
+ const compared = String(leftValue).localeCompare(String(rightValue));
14580
+ if (compared !== 0) {
14581
+ return compared;
14582
+ }
14583
+ }
14584
+ return 0;
14585
+ };
14586
+ var summarizeProofTrendProviders = (report, budgetMs) => {
14587
+ const sourceProviders = report.summary.providers && report.summary.providers.length > 0 ? report.summary.providers : undefined;
14588
+ const providersById = new Map;
14589
+ if (sourceProviders) {
14590
+ for (const provider of sourceProviders) {
14591
+ if (provider.id) {
14592
+ providersById.set(provider.id, provider);
14593
+ }
14594
+ }
14595
+ } else {
14596
+ for (const cycle of report.cycles) {
14597
+ for (const provider of cycle.providers ?? []) {
14598
+ if (!provider.id) {
14599
+ continue;
14600
+ }
14601
+ const existing = providersById.get(provider.id);
14602
+ providersById.set(provider.id, {
14603
+ averageMs: maxNumber([existing?.averageMs, provider.averageMs]),
14604
+ id: provider.id,
14605
+ label: existing?.label ?? provider.label,
14606
+ p50Ms: maxNumber([existing?.p50Ms, provider.p50Ms]),
14607
+ p95Ms: maxNumber([existing?.p95Ms, provider.p95Ms]),
14608
+ role: existing?.role ?? provider.role,
14609
+ samples: (existing?.samples ?? 0) + (provider.samples ?? 0),
14610
+ status: existing?.status === "fail" || provider.status === "fail" ? "fail" : existing?.status === "warn" || provider.status === "warn" ? "warn" : provider.status ?? existing?.status
14611
+ });
14612
+ }
14613
+ }
14614
+ }
14615
+ return [...providersById.values()].map((provider) => {
14616
+ const status = provider.p95Ms === undefined ? normalizeProviderStatus(provider.status) : withinBudget(provider.p95Ms, budgetMs) ? normalizeProviderStatus(provider.status) === "fail" ? "fail" : "pass" : normalizeProviderStatus(provider.status) === "fail" ? "fail" : "warn";
14617
+ return {
14618
+ averageMs: provider.averageMs,
14619
+ id: provider.id,
14620
+ label: provider.label,
14621
+ nextMove: status === "pass" ? "Eligible for latency-sensitive routing based on sustained proof." : provider.p95Ms === undefined ? "Collect provider-specific latency samples before routing latency-sensitive traffic here." : "Keep as fallback or tune provider/model/runtime budgets before using for latency-sensitive routing.",
14622
+ p50Ms: provider.p50Ms,
14623
+ p95Ms: provider.p95Ms,
14624
+ rank: 0,
14625
+ role: provider.role,
14626
+ samples: provider.samples,
14627
+ status
14628
+ };
14629
+ }).sort(compareProviders).map((provider, index) => ({ ...provider, rank: index + 1 }));
14630
+ };
14631
+ var shouldSwitchProvider = (current, best, options) => {
14632
+ if (!current || !best || current.id === best.id || best.status !== "pass") {
14633
+ return false;
14634
+ }
14635
+ if (current.p95Ms === undefined || best.p95Ms === undefined) {
14636
+ return false;
14637
+ }
14638
+ const minImprovementMs = options.providerSwitchMinImprovementMs ?? 100;
14639
+ const minImprovementRatio = options.providerSwitchMinImprovementRatio ?? 0.1;
14640
+ const improvementMs = current.p95Ms - best.p95Ms;
14641
+ const improvementRatio = current.p95Ms > 0 ? improvementMs / current.p95Ms : 0;
14642
+ return improvementMs >= minImprovementMs || improvementRatio >= minImprovementRatio;
14643
+ };
14644
+ var bestProviderByRole = (providers) => {
14645
+ const best = new Map;
14646
+ for (const provider of providers) {
14647
+ const role = provider.role ?? provider.id;
14648
+ const existing = best.get(role);
14649
+ if (!existing || compareProviders(provider, existing) < 0) {
14650
+ best.set(role, provider);
14651
+ }
14652
+ }
14653
+ return [...best.values()].sort((left, right) => String(left.role ?? left.id).localeCompare(String(right.role ?? right.id)));
14654
+ };
14655
+ var formatProviderMix = (providers) => providers.length === 0 ? "n/a" : providers.map((provider) => provider.role ? `${provider.role.toUpperCase()} ${provider.label ?? provider.id}` : provider.label ?? provider.id).join(", ");
14559
14656
  var evaluateVoiceProofTrendEvidence = (report, input = {}) => {
14560
14657
  const issues = [];
14561
14658
  const requiredStatus = input.requireStatus ?? "pass";
@@ -14670,6 +14767,12 @@ var buildVoiceProofTrendRecommendationReport = (report, options = {}) => {
14670
14767
  const maxProviderP95Ms = readProofTrendMaxProviderP95(report);
14671
14768
  const maxTurnP95Ms = readProofTrendMaxTurnP95(report);
14672
14769
  const runtimeChannel = readProofTrendRuntimeChannel(report);
14770
+ const providers = summarizeProofTrendProviders(report, budgets.maxProviderP95Ms);
14771
+ const bestProvider = providers.find((provider) => provider.status === "pass") ?? providers[0];
14772
+ const bestProviders = bestProviderByRole(providers).filter((provider) => provider.status === "pass");
14773
+ const currentProvider = options.currentProviderId ? providers.find((provider) => provider.id === options.currentProviderId) : undefined;
14774
+ const bestComparableProvider = currentProvider?.role ? bestProviders.find((provider) => provider.role === currentProvider.role) : bestProvider;
14775
+ const providerSwitchRecommended = shouldSwitchProvider(currentProvider, bestComparableProvider, options);
14673
14776
  const recommendations = [];
14674
14777
  const issues = [];
14675
14778
  if (report.ok !== true) {
@@ -14677,12 +14780,20 @@ var buildVoiceProofTrendRecommendationReport = (report, options = {}) => {
14677
14780
  }
14678
14781
  recommendations.push({
14679
14782
  evidence: {
14783
+ bestProviderId: bestComparableProvider?.id ?? bestProvider?.id,
14784
+ bestProviderMix: formatProviderMix(bestProviders),
14785
+ bestProviderP95Ms: bestComparableProvider?.p95Ms ?? bestProvider?.p95Ms,
14680
14786
  budgetMs: budgets.maxProviderP95Ms,
14787
+ currentProviderId: currentProvider?.id ?? options.currentProviderId,
14788
+ currentProviderP95Ms: currentProvider?.p95Ms,
14789
+ providerComparisonCount: providers.length,
14681
14790
  providerP95Ms: maxProviderP95Ms
14682
14791
  },
14683
- nextMove: withinBudget(maxProviderP95Ms, budgets.maxProviderP95Ms) ? "Keep the current provider route for latency-sensitive turns and keep collecting sustained proof." : "Route latency-sensitive turns to a faster provider profile or tighten fallback/circuit-breaker budgets before promotion.",
14684
- recommendation: withinBudget(maxProviderP95Ms, budgets.maxProviderP95Ms) ? "Keep current provider path" : "Change provider routing for latency-sensitive traffic",
14685
- status: withinBudget(maxProviderP95Ms, budgets.maxProviderP95Ms) ? "pass" : maxProviderP95Ms === undefined ? "fail" : "warn",
14792
+ nextMove: providers.length > 0 ? providerSwitchRecommended ? `Route latency-sensitive ${currentProvider?.role ?? "provider"} traffic to ${bestComparableProvider?.label ?? bestComparableProvider?.id} for this call profile and keep the current path as fallback.` : bestProviders.length > 0 ? `Use the fastest proven provider mix for this call profile: ${formatProviderMix(bestProviders)}.` : "Collect provider-specific sustained samples before making provider-specific routing decisions." : withinBudget(maxProviderP95Ms, budgets.maxProviderP95Ms) ? "Keep the current provider route for latency-sensitive turns and keep collecting sustained proof." : "Route latency-sensitive turns to a faster provider profile or tighten fallback/circuit-breaker budgets before promotion.",
14793
+ providerId: providerSwitchRecommended ? bestComparableProvider?.id : bestProviders.length === 1 ? bestProviders[0]?.id : undefined,
14794
+ recommendation: providers.length > 0 ? providerSwitchRecommended ? `Switch latency-sensitive ${currentProvider?.role ?? "provider"} routing to ${bestComparableProvider?.label ?? bestComparableProvider?.id}` : bestProviders.length > 0 ? "Prefer the fastest proven provider mix for this call profile" : "Collect provider-specific latency samples" : withinBudget(maxProviderP95Ms, budgets.maxProviderP95Ms) ? "Keep current provider path" : "Change provider routing for latency-sensitive traffic",
14795
+ role: bestComparableProvider?.role,
14796
+ status: providers.length > 0 ? providerSwitchRecommended ? "warn" : bestProviders.length > 0 ? "pass" : bestProvider?.status ?? "fail" : withinBudget(maxProviderP95Ms, budgets.maxProviderP95Ms) ? "pass" : maxProviderP95Ms === undefined ? "fail" : "warn",
14686
14797
  surface: "provider-path"
14687
14798
  });
14688
14799
  const runtimePass = withinBudget(runtimeChannel.maxFirstAudioLatencyMs, budgets.maxRuntimeFirstAudioLatencyMs) && withinBudget(runtimeChannel.maxInterruptionP95Ms, budgets.maxRuntimeInterruptionP95Ms) && withinBudget(runtimeChannel.maxJitterMs, budgets.maxRuntimeJitterMs) && withinBudget(runtimeChannel.maxTimestampDriftMs, budgets.maxRuntimeTimestampDriftMs) && withinBudget(runtimeChannel.maxBackpressureEvents, budgets.maxRuntimeBackpressureEvents);
@@ -14725,16 +14836,21 @@ var buildVoiceProofTrendRecommendationReport = (report, options = {}) => {
14725
14836
  });
14726
14837
  const status = issues.length > 0 ? "fail" : worstRecommendationStatus(recommendations);
14727
14838
  return {
14839
+ bestProvider,
14840
+ bestProviders,
14728
14841
  generatedAt: new Date().toISOString(),
14729
14842
  issues,
14730
14843
  ok: status !== "fail",
14844
+ providers,
14731
14845
  recommendations,
14732
14846
  source: report.source || report.outputDir || report.runId || "proof-trends",
14733
14847
  status,
14734
14848
  summary: {
14735
- keepCurrentProviderPath: recommendations.find((item) => item.surface === "provider-path")?.status === "pass",
14849
+ keepCurrentProviderPath: !providerSwitchRecommended && recommendations.find((item) => item.surface === "provider-path")?.status !== "fail",
14736
14850
  keepCurrentRuntimeChannel: recommendations.find((item) => item.surface === "runtime-channel")?.status === "pass",
14737
- recommendedActions: recommendations.filter((item) => item.status !== "pass").length
14851
+ providerComparisonCount: providers.length,
14852
+ recommendedActions: recommendations.filter((item) => item.status !== "pass").length,
14853
+ switchRecommended: providerSwitchRecommended
14738
14854
  }
14739
14855
  };
14740
14856
  };
@@ -14745,12 +14861,20 @@ var renderVoiceProofTrendRecommendationMarkdown = (report, title = "Voice Provid
14745
14861
  "",
14746
14862
  `- Status: ${report.status}`,
14747
14863
  `- Source: ${report.source}`,
14864
+ `- Best provider mix: ${formatProviderMix(report.bestProviders)}`,
14865
+ `- Provider comparisons: ${String(report.summary.providerComparisonCount)}`,
14748
14866
  `- Recommended actions: ${String(report.summary.recommendedActions)}`,
14749
14867
  "",
14750
14868
  "| Surface | Status | Recommendation | Next move |",
14751
14869
  "| --- | --- | --- | --- |",
14752
14870
  ...report.recommendations.map((recommendation) => `| ${escapeMarkdown2(recommendation.surface)} | ${recommendation.status} | ${escapeMarkdown2(recommendation.recommendation)} | ${escapeMarkdown2(recommendation.nextMove)} |`),
14753
14871
  "",
14872
+ "## Provider Comparison",
14873
+ "",
14874
+ "| Rank | Provider | Role | Status | P95 | Samples | Next move |",
14875
+ "| ---: | --- | --- | --- | ---: | ---: | --- |",
14876
+ ...report.providers.length ? report.providers.map((provider) => `| ${String(provider.rank)} | ${escapeMarkdown2(provider.label ?? provider.id)} | ${escapeMarkdown2(provider.role ?? "n/a")} | ${provider.status} | ${provider.p95Ms === undefined ? "n/a" : String(provider.p95Ms)} | ${provider.samples === undefined ? "n/a" : String(provider.samples)} | ${escapeMarkdown2(provider.nextMove)} |`) : ["| n/a | n/a | n/a | n/a | n/a | n/a | No provider-specific samples were present. |"],
14877
+ "",
14754
14878
  "## Issues",
14755
14879
  "",
14756
14880
  ...report.issues.length ? report.issues.map((issue) => `- ${issue}`) : ["- None"]
@@ -14759,7 +14883,8 @@ var renderVoiceProofTrendRecommendationMarkdown = (report, title = "Voice Provid
14759
14883
  var renderVoiceProofTrendRecommendationHTML = (report, title = "Voice Provider Runtime Recommendations") => {
14760
14884
  const cards = report.recommendations.map((recommendation) => `<article class="${escapeHtml22(recommendation.status)}"><p class="eyebrow">${escapeHtml22(recommendation.surface)} \xB7 ${escapeHtml22(recommendation.status)}</p><h2>${escapeHtml22(recommendation.recommendation)}</h2><p>${escapeHtml22(recommendation.nextMove)}</p><pre>${escapeHtml22(JSON.stringify(recommendation.evidence, null, 2))}</pre></article>`).join("");
14761
14885
  const issues = report.issues.length === 0 ? "<li>None</li>" : report.issues.map((issue) => `<li>${escapeHtml22(issue)}</li>`).join("");
14762
- return `<!doctype html><html lang="en"><head><meta charset="utf-8" /><meta name="viewport" content="width=device-width,initial-scale=1" /><title>${escapeHtml22(title)}</title><style>body{background:#101418;color:#f7f3e8;font-family:ui-sans-serif,system-ui,sans-serif;margin:0}main{margin:auto;max-width:1120px;padding:32px}.hero,article{background:#17201d;border:1px solid #2e3d36;border-radius:24px;margin-bottom:16px;padding:22px}.hero{background:linear-gradient(135deg,rgba(20,184,166,.18),rgba(245,158,11,.12))}.eyebrow{color:#5eead4;font-weight:900;letter-spacing:.1em;text-transform:uppercase}h1{font-size:clamp(2.2rem,6vw,4.7rem);letter-spacing:-.06em;line-height:.92;margin:.2rem 0 1rem}.summary{display:flex;flex-wrap:wrap;gap:10px}.pill{border:1px solid #42534a;border-radius:999px;padding:8px 12px}.pass{border-color:rgba(34,197,94,.55)}.warn{border-color:rgba(245,158,11,.7)}.fail{border-color:rgba(239,68,68,.75)}pre{background:#0b1110;border-radius:14px;overflow:auto;padding:12px}a{color:#5eead4}</style></head><body><main><section class="hero"><p class="eyebrow">Sustained proof recommendations</p><h1>${escapeHtml22(title)}</h1><p>Generated ${escapeHtml22(report.generatedAt)} from ${escapeHtml22(report.source)}.</p><div class="summary"><span class="pill">Status ${escapeHtml22(report.status)}</span><span class="pill">Provider ${report.summary.keepCurrentProviderPath ? "keep" : "change"}</span><span class="pill">Runtime ${report.summary.keepCurrentRuntimeChannel ? "keep" : "tune"}</span><span class="pill">${String(report.summary.recommendedActions)} action(s)</span></div></section>${cards}<section class="hero"><h2>Issues</h2><ul>${issues}</ul></section></main></body></html>`;
14886
+ const providerRows = report.providers.length === 0 ? "<li>No provider-specific samples were present.</li>" : report.providers.map((provider) => `<li><strong>#${String(provider.rank)} ${escapeHtml22(provider.label ?? provider.id)}</strong><span>${escapeHtml22(provider.role ?? "provider")} \xB7 ${escapeHtml22(provider.status)} \xB7 p95 ${escapeHtml22(provider.p95Ms ?? "n/a")}ms \xB7 ${escapeHtml22(provider.samples ?? "n/a")} sample(s)</span><small>${escapeHtml22(provider.nextMove)}</small></li>`).join("");
14887
+ return `<!doctype html><html lang="en"><head><meta charset="utf-8" /><meta name="viewport" content="width=device-width,initial-scale=1" /><title>${escapeHtml22(title)}</title><style>body{background:#101418;color:#f7f3e8;font-family:ui-sans-serif,system-ui,sans-serif;margin:0}main{margin:auto;max-width:1120px;padding:32px}.hero,article{background:#17201d;border:1px solid #2e3d36;border-radius:24px;margin-bottom:16px;padding:22px}.hero{background:linear-gradient(135deg,rgba(20,184,166,.18),rgba(245,158,11,.12))}.eyebrow{color:#5eead4;font-weight:900;letter-spacing:.1em;text-transform:uppercase}h1{font-size:clamp(2.2rem,6vw,4.7rem);letter-spacing:-.06em;line-height:.92;margin:.2rem 0 1rem}.summary{display:flex;flex-wrap:wrap;gap:10px}.pill{border:1px solid #42534a;border-radius:999px;padding:8px 12px}.pass{border-color:rgba(34,197,94,.55)}.warn{border-color:rgba(245,158,11,.7)}.fail{border-color:rgba(239,68,68,.75)}pre{background:#0b1110;border-radius:14px;overflow:auto;padding:12px}a{color:#5eead4}li{margin:.45rem 0}li span,li small{display:block;color:#c9d3ca}</style></head><body><main><section class="hero"><p class="eyebrow">Sustained proof recommendations</p><h1>${escapeHtml22(title)}</h1><p>Generated ${escapeHtml22(report.generatedAt)} from ${escapeHtml22(report.source)}.</p><div class="summary"><span class="pill">Status ${escapeHtml22(report.status)}</span><span class="pill">Provider ${report.summary.keepCurrentProviderPath ? "keep" : "change"}</span><span class="pill">Best mix ${escapeHtml22(formatProviderMix(report.bestProviders))}</span><span class="pill">Runtime ${report.summary.keepCurrentRuntimeChannel ? "keep" : "tune"}</span><span class="pill">${String(report.summary.recommendedActions)} action(s)</span></div></section>${cards}<section class="hero"><h2>Provider Comparison</h2><ul>${providerRows}</ul></section><section class="hero"><h2>Issues</h2><ul>${issues}</ul></section></main></body></html>`;
14763
14888
  };
14764
14889
  var createVoiceProofTrendRecommendationRoutes = (options) => {
14765
14890
  const path = options.path ?? "/api/voice/proof-trend-recommendations";
@@ -4,9 +4,20 @@ export type VoiceProofTrendSummary = {
4
4
  cycles?: number;
5
5
  maxLiveP95Ms?: number;
6
6
  maxProviderP95Ms?: number;
7
+ providers?: VoiceProofTrendProviderSummary[];
7
8
  runtimeChannel?: VoiceProofTrendRuntimeChannelSummary;
8
9
  maxTurnP95Ms?: number;
9
10
  };
11
+ export type VoiceProofTrendProviderSummary = {
12
+ averageMs?: number;
13
+ id: string;
14
+ label?: string;
15
+ p50Ms?: number;
16
+ p95Ms?: number;
17
+ role?: string;
18
+ samples?: number;
19
+ status?: string;
20
+ };
10
21
  export type VoiceProofTrendRuntimeChannelSummary = {
11
22
  maxBackpressureEvents?: number;
12
23
  maxFirstAudioLatencyMs?: number;
@@ -36,6 +47,7 @@ export type VoiceProofTrendCycle = {
36
47
  eventsWithLatency?: number;
37
48
  status?: string;
38
49
  };
50
+ providers?: VoiceProofTrendProviderSummary[];
39
51
  runtimeChannel?: VoiceProofTrendRuntimeChannelSummary;
40
52
  turnLatency?: {
41
53
  p95Ms?: number;
@@ -113,24 +125,44 @@ export type VoiceProofTrendRecommendationSurface = 'live-latency' | 'provider-pa
113
125
  export type VoiceProofTrendRecommendation = {
114
126
  evidence: Record<string, number | string | undefined>;
115
127
  nextMove: string;
128
+ providerId?: string;
129
+ role?: string;
116
130
  recommendation: string;
117
131
  status: VoiceProofTrendRecommendationStatus;
118
132
  surface: VoiceProofTrendRecommendationSurface;
119
133
  };
134
+ export type VoiceProofTrendProviderRecommendation = {
135
+ averageMs?: number;
136
+ id: string;
137
+ label?: string;
138
+ nextMove: string;
139
+ p50Ms?: number;
140
+ p95Ms?: number;
141
+ rank: number;
142
+ role?: string;
143
+ samples?: number;
144
+ status: VoiceProofTrendRecommendationStatus;
145
+ };
120
146
  export type VoiceProofTrendRecommendationReport = {
147
+ bestProvider?: VoiceProofTrendProviderRecommendation;
148
+ bestProviders: VoiceProofTrendProviderRecommendation[];
121
149
  generatedAt: string;
122
150
  issues: string[];
123
151
  ok: boolean;
152
+ providers: VoiceProofTrendProviderRecommendation[];
124
153
  recommendations: VoiceProofTrendRecommendation[];
125
154
  source: string;
126
155
  status: VoiceProofTrendRecommendationStatus;
127
156
  summary: {
128
157
  keepCurrentProviderPath: boolean;
129
158
  keepCurrentRuntimeChannel: boolean;
159
+ providerComparisonCount: number;
130
160
  recommendedActions: number;
161
+ switchRecommended: boolean;
131
162
  };
132
163
  };
133
164
  export type VoiceProofTrendRecommendationOptions = {
165
+ currentProviderId?: string;
134
166
  maxLiveP95Ms?: number;
135
167
  maxProviderP95Ms?: number;
136
168
  maxRuntimeBackpressureEvents?: number;
@@ -139,6 +171,8 @@ export type VoiceProofTrendRecommendationOptions = {
139
171
  maxRuntimeJitterMs?: number;
140
172
  maxRuntimeTimestampDriftMs?: number;
141
173
  maxTurnP95Ms?: number;
174
+ providerSwitchMinImprovementMs?: number;
175
+ providerSwitchMinImprovementRatio?: number;
142
176
  };
143
177
  export type VoiceProofTrendRecommendationRoutesOptions = VoiceProofTrendRecommendationOptions & {
144
178
  headers?: HeadersInit;
@@ -1569,7 +1569,7 @@ var maxNumber = (values) => {
1569
1569
  return finite.length > 0 ? Math.max(...finite) : undefined;
1570
1570
  };
1571
1571
  var readProofTrendMaxLiveP95 = (report) => report.summary.maxLiveP95Ms ?? maxNumber(report.cycles.map((cycle) => cycle.liveLatency?.p95Ms));
1572
- var readProofTrendMaxProviderP95 = (report) => report.summary.maxProviderP95Ms;
1572
+ 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)));
1573
1573
  var readProofTrendMaxTurnP95 = (report) => report.summary.maxTurnP95Ms ?? maxNumber(report.cycles.map((cycle) => cycle.turnLatency?.p95Ms));
1574
1574
  var readRuntimeChannelMetric = (report, key) => {
1575
1575
  const summaryValue = report.summary.runtimeChannel?.[key];
@@ -1587,6 +1587,103 @@ var readProofTrendRuntimeChannel = (report) => ({
1587
1587
  samples: report.summary.runtimeChannel?.samples ?? maxNumber(report.cycles.map((cycle) => cycle.runtimeChannel?.samples)),
1588
1588
  status: report.summary.runtimeChannel?.status
1589
1589
  });
1590
+ var normalizeProviderStatus = (status) => status === "pass" ? "pass" : status === "fail" ? "fail" : "warn";
1591
+ var providerSortScore = (provider) => [
1592
+ recommendationStatusRank[provider.status],
1593
+ provider.p95Ms ?? Number.POSITIVE_INFINITY,
1594
+ provider.averageMs ?? Number.POSITIVE_INFINITY,
1595
+ provider.samples === undefined ? Number.POSITIVE_INFINITY : -provider.samples,
1596
+ provider.id
1597
+ ];
1598
+ var compareProviders = (left, right) => {
1599
+ const leftScore = providerSortScore(left);
1600
+ const rightScore = providerSortScore(right);
1601
+ for (let index = 0;index < leftScore.length; index += 1) {
1602
+ const leftValue = leftScore[index];
1603
+ const rightValue = rightScore[index];
1604
+ if (typeof leftValue === "number" && typeof rightValue === "number") {
1605
+ if (leftValue !== rightValue) {
1606
+ return leftValue - rightValue;
1607
+ }
1608
+ continue;
1609
+ }
1610
+ const compared = String(leftValue).localeCompare(String(rightValue));
1611
+ if (compared !== 0) {
1612
+ return compared;
1613
+ }
1614
+ }
1615
+ return 0;
1616
+ };
1617
+ var summarizeProofTrendProviders = (report, budgetMs) => {
1618
+ const sourceProviders = report.summary.providers && report.summary.providers.length > 0 ? report.summary.providers : undefined;
1619
+ const providersById = new Map;
1620
+ if (sourceProviders) {
1621
+ for (const provider of sourceProviders) {
1622
+ if (provider.id) {
1623
+ providersById.set(provider.id, provider);
1624
+ }
1625
+ }
1626
+ } else {
1627
+ for (const cycle of report.cycles) {
1628
+ for (const provider of cycle.providers ?? []) {
1629
+ if (!provider.id) {
1630
+ continue;
1631
+ }
1632
+ const existing = providersById.get(provider.id);
1633
+ providersById.set(provider.id, {
1634
+ averageMs: maxNumber([existing?.averageMs, provider.averageMs]),
1635
+ id: provider.id,
1636
+ label: existing?.label ?? provider.label,
1637
+ p50Ms: maxNumber([existing?.p50Ms, provider.p50Ms]),
1638
+ p95Ms: maxNumber([existing?.p95Ms, provider.p95Ms]),
1639
+ role: existing?.role ?? provider.role,
1640
+ samples: (existing?.samples ?? 0) + (provider.samples ?? 0),
1641
+ status: existing?.status === "fail" || provider.status === "fail" ? "fail" : existing?.status === "warn" || provider.status === "warn" ? "warn" : provider.status ?? existing?.status
1642
+ });
1643
+ }
1644
+ }
1645
+ }
1646
+ return [...providersById.values()].map((provider) => {
1647
+ const status = provider.p95Ms === undefined ? normalizeProviderStatus(provider.status) : withinBudget(provider.p95Ms, budgetMs) ? normalizeProviderStatus(provider.status) === "fail" ? "fail" : "pass" : normalizeProviderStatus(provider.status) === "fail" ? "fail" : "warn";
1648
+ return {
1649
+ averageMs: provider.averageMs,
1650
+ id: provider.id,
1651
+ label: provider.label,
1652
+ nextMove: status === "pass" ? "Eligible for latency-sensitive routing based on sustained proof." : provider.p95Ms === undefined ? "Collect provider-specific latency samples before routing latency-sensitive traffic here." : "Keep as fallback or tune provider/model/runtime budgets before using for latency-sensitive routing.",
1653
+ p50Ms: provider.p50Ms,
1654
+ p95Ms: provider.p95Ms,
1655
+ rank: 0,
1656
+ role: provider.role,
1657
+ samples: provider.samples,
1658
+ status
1659
+ };
1660
+ }).sort(compareProviders).map((provider, index) => ({ ...provider, rank: index + 1 }));
1661
+ };
1662
+ var shouldSwitchProvider = (current, best, options) => {
1663
+ if (!current || !best || current.id === best.id || best.status !== "pass") {
1664
+ return false;
1665
+ }
1666
+ if (current.p95Ms === undefined || best.p95Ms === undefined) {
1667
+ return false;
1668
+ }
1669
+ const minImprovementMs = options.providerSwitchMinImprovementMs ?? 100;
1670
+ const minImprovementRatio = options.providerSwitchMinImprovementRatio ?? 0.1;
1671
+ const improvementMs = current.p95Ms - best.p95Ms;
1672
+ const improvementRatio = current.p95Ms > 0 ? improvementMs / current.p95Ms : 0;
1673
+ return improvementMs >= minImprovementMs || improvementRatio >= minImprovementRatio;
1674
+ };
1675
+ var bestProviderByRole = (providers) => {
1676
+ const best = new Map;
1677
+ for (const provider of providers) {
1678
+ const role = provider.role ?? provider.id;
1679
+ const existing = best.get(role);
1680
+ if (!existing || compareProviders(provider, existing) < 0) {
1681
+ best.set(role, provider);
1682
+ }
1683
+ }
1684
+ return [...best.values()].sort((left, right) => String(left.role ?? left.id).localeCompare(String(right.role ?? right.id)));
1685
+ };
1686
+ var formatProviderMix = (providers) => providers.length === 0 ? "n/a" : providers.map((provider) => provider.role ? `${provider.role.toUpperCase()} ${provider.label ?? provider.id}` : provider.label ?? provider.id).join(", ");
1590
1687
  var evaluateVoiceProofTrendEvidence = (report, input = {}) => {
1591
1688
  const issues = [];
1592
1689
  const requiredStatus = input.requireStatus ?? "pass";
@@ -1701,6 +1798,12 @@ var buildVoiceProofTrendRecommendationReport = (report, options = {}) => {
1701
1798
  const maxProviderP95Ms = readProofTrendMaxProviderP95(report);
1702
1799
  const maxTurnP95Ms = readProofTrendMaxTurnP95(report);
1703
1800
  const runtimeChannel = readProofTrendRuntimeChannel(report);
1801
+ const providers = summarizeProofTrendProviders(report, budgets.maxProviderP95Ms);
1802
+ const bestProvider = providers.find((provider) => provider.status === "pass") ?? providers[0];
1803
+ const bestProviders = bestProviderByRole(providers).filter((provider) => provider.status === "pass");
1804
+ const currentProvider = options.currentProviderId ? providers.find((provider) => provider.id === options.currentProviderId) : undefined;
1805
+ const bestComparableProvider = currentProvider?.role ? bestProviders.find((provider) => provider.role === currentProvider.role) : bestProvider;
1806
+ const providerSwitchRecommended = shouldSwitchProvider(currentProvider, bestComparableProvider, options);
1704
1807
  const recommendations = [];
1705
1808
  const issues = [];
1706
1809
  if (report.ok !== true) {
@@ -1708,12 +1811,20 @@ var buildVoiceProofTrendRecommendationReport = (report, options = {}) => {
1708
1811
  }
1709
1812
  recommendations.push({
1710
1813
  evidence: {
1814
+ bestProviderId: bestComparableProvider?.id ?? bestProvider?.id,
1815
+ bestProviderMix: formatProviderMix(bestProviders),
1816
+ bestProviderP95Ms: bestComparableProvider?.p95Ms ?? bestProvider?.p95Ms,
1711
1817
  budgetMs: budgets.maxProviderP95Ms,
1818
+ currentProviderId: currentProvider?.id ?? options.currentProviderId,
1819
+ currentProviderP95Ms: currentProvider?.p95Ms,
1820
+ providerComparisonCount: providers.length,
1712
1821
  providerP95Ms: maxProviderP95Ms
1713
1822
  },
1714
- nextMove: withinBudget(maxProviderP95Ms, budgets.maxProviderP95Ms) ? "Keep the current provider route for latency-sensitive turns and keep collecting sustained proof." : "Route latency-sensitive turns to a faster provider profile or tighten fallback/circuit-breaker budgets before promotion.",
1715
- recommendation: withinBudget(maxProviderP95Ms, budgets.maxProviderP95Ms) ? "Keep current provider path" : "Change provider routing for latency-sensitive traffic",
1716
- status: withinBudget(maxProviderP95Ms, budgets.maxProviderP95Ms) ? "pass" : maxProviderP95Ms === undefined ? "fail" : "warn",
1823
+ nextMove: providers.length > 0 ? providerSwitchRecommended ? `Route latency-sensitive ${currentProvider?.role ?? "provider"} traffic to ${bestComparableProvider?.label ?? bestComparableProvider?.id} for this call profile and keep the current path as fallback.` : bestProviders.length > 0 ? `Use the fastest proven provider mix for this call profile: ${formatProviderMix(bestProviders)}.` : "Collect provider-specific sustained samples before making provider-specific routing decisions." : withinBudget(maxProviderP95Ms, budgets.maxProviderP95Ms) ? "Keep the current provider route for latency-sensitive turns and keep collecting sustained proof." : "Route latency-sensitive turns to a faster provider profile or tighten fallback/circuit-breaker budgets before promotion.",
1824
+ providerId: providerSwitchRecommended ? bestComparableProvider?.id : bestProviders.length === 1 ? bestProviders[0]?.id : undefined,
1825
+ recommendation: providers.length > 0 ? providerSwitchRecommended ? `Switch latency-sensitive ${currentProvider?.role ?? "provider"} routing to ${bestComparableProvider?.label ?? bestComparableProvider?.id}` : bestProviders.length > 0 ? "Prefer the fastest proven provider mix for this call profile" : "Collect provider-specific latency samples" : withinBudget(maxProviderP95Ms, budgets.maxProviderP95Ms) ? "Keep current provider path" : "Change provider routing for latency-sensitive traffic",
1826
+ role: bestComparableProvider?.role,
1827
+ status: providers.length > 0 ? providerSwitchRecommended ? "warn" : bestProviders.length > 0 ? "pass" : bestProvider?.status ?? "fail" : withinBudget(maxProviderP95Ms, budgets.maxProviderP95Ms) ? "pass" : maxProviderP95Ms === undefined ? "fail" : "warn",
1717
1828
  surface: "provider-path"
1718
1829
  });
1719
1830
  const runtimePass = withinBudget(runtimeChannel.maxFirstAudioLatencyMs, budgets.maxRuntimeFirstAudioLatencyMs) && withinBudget(runtimeChannel.maxInterruptionP95Ms, budgets.maxRuntimeInterruptionP95Ms) && withinBudget(runtimeChannel.maxJitterMs, budgets.maxRuntimeJitterMs) && withinBudget(runtimeChannel.maxTimestampDriftMs, budgets.maxRuntimeTimestampDriftMs) && withinBudget(runtimeChannel.maxBackpressureEvents, budgets.maxRuntimeBackpressureEvents);
@@ -1756,16 +1867,21 @@ var buildVoiceProofTrendRecommendationReport = (report, options = {}) => {
1756
1867
  });
1757
1868
  const status = issues.length > 0 ? "fail" : worstRecommendationStatus(recommendations);
1758
1869
  return {
1870
+ bestProvider,
1871
+ bestProviders,
1759
1872
  generatedAt: new Date().toISOString(),
1760
1873
  issues,
1761
1874
  ok: status !== "fail",
1875
+ providers,
1762
1876
  recommendations,
1763
1877
  source: report.source || report.outputDir || report.runId || "proof-trends",
1764
1878
  status,
1765
1879
  summary: {
1766
- keepCurrentProviderPath: recommendations.find((item) => item.surface === "provider-path")?.status === "pass",
1880
+ keepCurrentProviderPath: !providerSwitchRecommended && recommendations.find((item) => item.surface === "provider-path")?.status !== "fail",
1767
1881
  keepCurrentRuntimeChannel: recommendations.find((item) => item.surface === "runtime-channel")?.status === "pass",
1768
- recommendedActions: recommendations.filter((item) => item.status !== "pass").length
1882
+ providerComparisonCount: providers.length,
1883
+ recommendedActions: recommendations.filter((item) => item.status !== "pass").length,
1884
+ switchRecommended: providerSwitchRecommended
1769
1885
  }
1770
1886
  };
1771
1887
  };
@@ -1776,12 +1892,20 @@ var renderVoiceProofTrendRecommendationMarkdown = (report, title = "Voice Provid
1776
1892
  "",
1777
1893
  `- Status: ${report.status}`,
1778
1894
  `- Source: ${report.source}`,
1895
+ `- Best provider mix: ${formatProviderMix(report.bestProviders)}`,
1896
+ `- Provider comparisons: ${String(report.summary.providerComparisonCount)}`,
1779
1897
  `- Recommended actions: ${String(report.summary.recommendedActions)}`,
1780
1898
  "",
1781
1899
  "| Surface | Status | Recommendation | Next move |",
1782
1900
  "| --- | --- | --- | --- |",
1783
1901
  ...report.recommendations.map((recommendation) => `| ${escapeMarkdown(recommendation.surface)} | ${recommendation.status} | ${escapeMarkdown(recommendation.recommendation)} | ${escapeMarkdown(recommendation.nextMove)} |`),
1784
1902
  "",
1903
+ "## Provider Comparison",
1904
+ "",
1905
+ "| Rank | Provider | Role | Status | P95 | Samples | Next move |",
1906
+ "| ---: | --- | --- | --- | ---: | ---: | --- |",
1907
+ ...report.providers.length ? report.providers.map((provider) => `| ${String(provider.rank)} | ${escapeMarkdown(provider.label ?? provider.id)} | ${escapeMarkdown(provider.role ?? "n/a")} | ${provider.status} | ${provider.p95Ms === undefined ? "n/a" : String(provider.p95Ms)} | ${provider.samples === undefined ? "n/a" : String(provider.samples)} | ${escapeMarkdown(provider.nextMove)} |`) : ["| n/a | n/a | n/a | n/a | n/a | n/a | No provider-specific samples were present. |"],
1908
+ "",
1785
1909
  "## Issues",
1786
1910
  "",
1787
1911
  ...report.issues.length ? report.issues.map((issue) => `- ${issue}`) : ["- None"]
@@ -1790,7 +1914,8 @@ var renderVoiceProofTrendRecommendationMarkdown = (report, title = "Voice Provid
1790
1914
  var renderVoiceProofTrendRecommendationHTML = (report, title = "Voice Provider Runtime Recommendations") => {
1791
1915
  const cards = report.recommendations.map((recommendation) => `<article class="${escapeHtml5(recommendation.status)}"><p class="eyebrow">${escapeHtml5(recommendation.surface)} \xB7 ${escapeHtml5(recommendation.status)}</p><h2>${escapeHtml5(recommendation.recommendation)}</h2><p>${escapeHtml5(recommendation.nextMove)}</p><pre>${escapeHtml5(JSON.stringify(recommendation.evidence, null, 2))}</pre></article>`).join("");
1792
1916
  const issues = report.issues.length === 0 ? "<li>None</li>" : report.issues.map((issue) => `<li>${escapeHtml5(issue)}</li>`).join("");
1793
- return `<!doctype html><html lang="en"><head><meta charset="utf-8" /><meta name="viewport" content="width=device-width,initial-scale=1" /><title>${escapeHtml5(title)}</title><style>body{background:#101418;color:#f7f3e8;font-family:ui-sans-serif,system-ui,sans-serif;margin:0}main{margin:auto;max-width:1120px;padding:32px}.hero,article{background:#17201d;border:1px solid #2e3d36;border-radius:24px;margin-bottom:16px;padding:22px}.hero{background:linear-gradient(135deg,rgba(20,184,166,.18),rgba(245,158,11,.12))}.eyebrow{color:#5eead4;font-weight:900;letter-spacing:.1em;text-transform:uppercase}h1{font-size:clamp(2.2rem,6vw,4.7rem);letter-spacing:-.06em;line-height:.92;margin:.2rem 0 1rem}.summary{display:flex;flex-wrap:wrap;gap:10px}.pill{border:1px solid #42534a;border-radius:999px;padding:8px 12px}.pass{border-color:rgba(34,197,94,.55)}.warn{border-color:rgba(245,158,11,.7)}.fail{border-color:rgba(239,68,68,.75)}pre{background:#0b1110;border-radius:14px;overflow:auto;padding:12px}a{color:#5eead4}</style></head><body><main><section class="hero"><p class="eyebrow">Sustained proof recommendations</p><h1>${escapeHtml5(title)}</h1><p>Generated ${escapeHtml5(report.generatedAt)} from ${escapeHtml5(report.source)}.</p><div class="summary"><span class="pill">Status ${escapeHtml5(report.status)}</span><span class="pill">Provider ${report.summary.keepCurrentProviderPath ? "keep" : "change"}</span><span class="pill">Runtime ${report.summary.keepCurrentRuntimeChannel ? "keep" : "tune"}</span><span class="pill">${String(report.summary.recommendedActions)} action(s)</span></div></section>${cards}<section class="hero"><h2>Issues</h2><ul>${issues}</ul></section></main></body></html>`;
1917
+ const providerRows = report.providers.length === 0 ? "<li>No provider-specific samples were present.</li>" : report.providers.map((provider) => `<li><strong>#${String(provider.rank)} ${escapeHtml5(provider.label ?? provider.id)}</strong><span>${escapeHtml5(provider.role ?? "provider")} \xB7 ${escapeHtml5(provider.status)} \xB7 p95 ${escapeHtml5(provider.p95Ms ?? "n/a")}ms \xB7 ${escapeHtml5(provider.samples ?? "n/a")} sample(s)</span><small>${escapeHtml5(provider.nextMove)}</small></li>`).join("");
1918
+ return `<!doctype html><html lang="en"><head><meta charset="utf-8" /><meta name="viewport" content="width=device-width,initial-scale=1" /><title>${escapeHtml5(title)}</title><style>body{background:#101418;color:#f7f3e8;font-family:ui-sans-serif,system-ui,sans-serif;margin:0}main{margin:auto;max-width:1120px;padding:32px}.hero,article{background:#17201d;border:1px solid #2e3d36;border-radius:24px;margin-bottom:16px;padding:22px}.hero{background:linear-gradient(135deg,rgba(20,184,166,.18),rgba(245,158,11,.12))}.eyebrow{color:#5eead4;font-weight:900;letter-spacing:.1em;text-transform:uppercase}h1{font-size:clamp(2.2rem,6vw,4.7rem);letter-spacing:-.06em;line-height:.92;margin:.2rem 0 1rem}.summary{display:flex;flex-wrap:wrap;gap:10px}.pill{border:1px solid #42534a;border-radius:999px;padding:8px 12px}.pass{border-color:rgba(34,197,94,.55)}.warn{border-color:rgba(245,158,11,.7)}.fail{border-color:rgba(239,68,68,.75)}pre{background:#0b1110;border-radius:14px;overflow:auto;padding:12px}a{color:#5eead4}li{margin:.45rem 0}li span,li small{display:block;color:#c9d3ca}</style></head><body><main><section class="hero"><p class="eyebrow">Sustained proof recommendations</p><h1>${escapeHtml5(title)}</h1><p>Generated ${escapeHtml5(report.generatedAt)} from ${escapeHtml5(report.source)}.</p><div class="summary"><span class="pill">Status ${escapeHtml5(report.status)}</span><span class="pill">Provider ${report.summary.keepCurrentProviderPath ? "keep" : "change"}</span><span class="pill">Best mix ${escapeHtml5(formatProviderMix(report.bestProviders))}</span><span class="pill">Runtime ${report.summary.keepCurrentRuntimeChannel ? "keep" : "tune"}</span><span class="pill">${String(report.summary.recommendedActions)} action(s)</span></div></section>${cards}<section class="hero"><h2>Provider Comparison</h2><ul>${providerRows}</ul></section><section class="hero"><h2>Issues</h2><ul>${issues}</ul></section></main></body></html>`;
1794
1919
  };
1795
1920
  var createVoiceProofTrendRecommendationRoutes = (options) => {
1796
1921
  const path = options.path ?? "/api/voice/proof-trend-recommendations";
package/dist/vue/index.js CHANGED
@@ -1490,7 +1490,7 @@ var maxNumber = (values) => {
1490
1490
  return finite.length > 0 ? Math.max(...finite) : undefined;
1491
1491
  };
1492
1492
  var readProofTrendMaxLiveP95 = (report) => report.summary.maxLiveP95Ms ?? maxNumber(report.cycles.map((cycle) => cycle.liveLatency?.p95Ms));
1493
- var readProofTrendMaxProviderP95 = (report) => report.summary.maxProviderP95Ms;
1493
+ 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)));
1494
1494
  var readProofTrendMaxTurnP95 = (report) => report.summary.maxTurnP95Ms ?? maxNumber(report.cycles.map((cycle) => cycle.turnLatency?.p95Ms));
1495
1495
  var readRuntimeChannelMetric = (report, key) => {
1496
1496
  const summaryValue = report.summary.runtimeChannel?.[key];
@@ -1508,6 +1508,103 @@ var readProofTrendRuntimeChannel = (report) => ({
1508
1508
  samples: report.summary.runtimeChannel?.samples ?? maxNumber(report.cycles.map((cycle) => cycle.runtimeChannel?.samples)),
1509
1509
  status: report.summary.runtimeChannel?.status
1510
1510
  });
1511
+ var normalizeProviderStatus = (status) => status === "pass" ? "pass" : status === "fail" ? "fail" : "warn";
1512
+ var providerSortScore = (provider) => [
1513
+ recommendationStatusRank[provider.status],
1514
+ provider.p95Ms ?? Number.POSITIVE_INFINITY,
1515
+ provider.averageMs ?? Number.POSITIVE_INFINITY,
1516
+ provider.samples === undefined ? Number.POSITIVE_INFINITY : -provider.samples,
1517
+ provider.id
1518
+ ];
1519
+ var compareProviders = (left, right) => {
1520
+ const leftScore = providerSortScore(left);
1521
+ const rightScore = providerSortScore(right);
1522
+ for (let index = 0;index < leftScore.length; index += 1) {
1523
+ const leftValue = leftScore[index];
1524
+ const rightValue = rightScore[index];
1525
+ if (typeof leftValue === "number" && typeof rightValue === "number") {
1526
+ if (leftValue !== rightValue) {
1527
+ return leftValue - rightValue;
1528
+ }
1529
+ continue;
1530
+ }
1531
+ const compared = String(leftValue).localeCompare(String(rightValue));
1532
+ if (compared !== 0) {
1533
+ return compared;
1534
+ }
1535
+ }
1536
+ return 0;
1537
+ };
1538
+ var summarizeProofTrendProviders = (report, budgetMs) => {
1539
+ const sourceProviders = report.summary.providers && report.summary.providers.length > 0 ? report.summary.providers : undefined;
1540
+ const providersById = new Map;
1541
+ if (sourceProviders) {
1542
+ for (const provider of sourceProviders) {
1543
+ if (provider.id) {
1544
+ providersById.set(provider.id, provider);
1545
+ }
1546
+ }
1547
+ } else {
1548
+ for (const cycle of report.cycles) {
1549
+ for (const provider of cycle.providers ?? []) {
1550
+ if (!provider.id) {
1551
+ continue;
1552
+ }
1553
+ const existing = providersById.get(provider.id);
1554
+ providersById.set(provider.id, {
1555
+ averageMs: maxNumber([existing?.averageMs, provider.averageMs]),
1556
+ id: provider.id,
1557
+ label: existing?.label ?? provider.label,
1558
+ p50Ms: maxNumber([existing?.p50Ms, provider.p50Ms]),
1559
+ p95Ms: maxNumber([existing?.p95Ms, provider.p95Ms]),
1560
+ role: existing?.role ?? provider.role,
1561
+ samples: (existing?.samples ?? 0) + (provider.samples ?? 0),
1562
+ status: existing?.status === "fail" || provider.status === "fail" ? "fail" : existing?.status === "warn" || provider.status === "warn" ? "warn" : provider.status ?? existing?.status
1563
+ });
1564
+ }
1565
+ }
1566
+ }
1567
+ return [...providersById.values()].map((provider) => {
1568
+ const status = provider.p95Ms === undefined ? normalizeProviderStatus(provider.status) : withinBudget(provider.p95Ms, budgetMs) ? normalizeProviderStatus(provider.status) === "fail" ? "fail" : "pass" : normalizeProviderStatus(provider.status) === "fail" ? "fail" : "warn";
1569
+ return {
1570
+ averageMs: provider.averageMs,
1571
+ id: provider.id,
1572
+ label: provider.label,
1573
+ nextMove: status === "pass" ? "Eligible for latency-sensitive routing based on sustained proof." : provider.p95Ms === undefined ? "Collect provider-specific latency samples before routing latency-sensitive traffic here." : "Keep as fallback or tune provider/model/runtime budgets before using for latency-sensitive routing.",
1574
+ p50Ms: provider.p50Ms,
1575
+ p95Ms: provider.p95Ms,
1576
+ rank: 0,
1577
+ role: provider.role,
1578
+ samples: provider.samples,
1579
+ status
1580
+ };
1581
+ }).sort(compareProviders).map((provider, index) => ({ ...provider, rank: index + 1 }));
1582
+ };
1583
+ var shouldSwitchProvider = (current, best, options) => {
1584
+ if (!current || !best || current.id === best.id || best.status !== "pass") {
1585
+ return false;
1586
+ }
1587
+ if (current.p95Ms === undefined || best.p95Ms === undefined) {
1588
+ return false;
1589
+ }
1590
+ const minImprovementMs = options.providerSwitchMinImprovementMs ?? 100;
1591
+ const minImprovementRatio = options.providerSwitchMinImprovementRatio ?? 0.1;
1592
+ const improvementMs = current.p95Ms - best.p95Ms;
1593
+ const improvementRatio = current.p95Ms > 0 ? improvementMs / current.p95Ms : 0;
1594
+ return improvementMs >= minImprovementMs || improvementRatio >= minImprovementRatio;
1595
+ };
1596
+ var bestProviderByRole = (providers) => {
1597
+ const best = new Map;
1598
+ for (const provider of providers) {
1599
+ const role = provider.role ?? provider.id;
1600
+ const existing = best.get(role);
1601
+ if (!existing || compareProviders(provider, existing) < 0) {
1602
+ best.set(role, provider);
1603
+ }
1604
+ }
1605
+ return [...best.values()].sort((left, right) => String(left.role ?? left.id).localeCompare(String(right.role ?? right.id)));
1606
+ };
1607
+ var formatProviderMix = (providers) => providers.length === 0 ? "n/a" : providers.map((provider) => provider.role ? `${provider.role.toUpperCase()} ${provider.label ?? provider.id}` : provider.label ?? provider.id).join(", ");
1511
1608
  var evaluateVoiceProofTrendEvidence = (report, input = {}) => {
1512
1609
  const issues = [];
1513
1610
  const requiredStatus = input.requireStatus ?? "pass";
@@ -1622,6 +1719,12 @@ var buildVoiceProofTrendRecommendationReport = (report, options = {}) => {
1622
1719
  const maxProviderP95Ms = readProofTrendMaxProviderP95(report);
1623
1720
  const maxTurnP95Ms = readProofTrendMaxTurnP95(report);
1624
1721
  const runtimeChannel = readProofTrendRuntimeChannel(report);
1722
+ const providers = summarizeProofTrendProviders(report, budgets.maxProviderP95Ms);
1723
+ const bestProvider = providers.find((provider) => provider.status === "pass") ?? providers[0];
1724
+ const bestProviders = bestProviderByRole(providers).filter((provider) => provider.status === "pass");
1725
+ const currentProvider = options.currentProviderId ? providers.find((provider) => provider.id === options.currentProviderId) : undefined;
1726
+ const bestComparableProvider = currentProvider?.role ? bestProviders.find((provider) => provider.role === currentProvider.role) : bestProvider;
1727
+ const providerSwitchRecommended = shouldSwitchProvider(currentProvider, bestComparableProvider, options);
1625
1728
  const recommendations = [];
1626
1729
  const issues = [];
1627
1730
  if (report.ok !== true) {
@@ -1629,12 +1732,20 @@ var buildVoiceProofTrendRecommendationReport = (report, options = {}) => {
1629
1732
  }
1630
1733
  recommendations.push({
1631
1734
  evidence: {
1735
+ bestProviderId: bestComparableProvider?.id ?? bestProvider?.id,
1736
+ bestProviderMix: formatProviderMix(bestProviders),
1737
+ bestProviderP95Ms: bestComparableProvider?.p95Ms ?? bestProvider?.p95Ms,
1632
1738
  budgetMs: budgets.maxProviderP95Ms,
1739
+ currentProviderId: currentProvider?.id ?? options.currentProviderId,
1740
+ currentProviderP95Ms: currentProvider?.p95Ms,
1741
+ providerComparisonCount: providers.length,
1633
1742
  providerP95Ms: maxProviderP95Ms
1634
1743
  },
1635
- nextMove: withinBudget(maxProviderP95Ms, budgets.maxProviderP95Ms) ? "Keep the current provider route for latency-sensitive turns and keep collecting sustained proof." : "Route latency-sensitive turns to a faster provider profile or tighten fallback/circuit-breaker budgets before promotion.",
1636
- recommendation: withinBudget(maxProviderP95Ms, budgets.maxProviderP95Ms) ? "Keep current provider path" : "Change provider routing for latency-sensitive traffic",
1637
- status: withinBudget(maxProviderP95Ms, budgets.maxProviderP95Ms) ? "pass" : maxProviderP95Ms === undefined ? "fail" : "warn",
1744
+ nextMove: providers.length > 0 ? providerSwitchRecommended ? `Route latency-sensitive ${currentProvider?.role ?? "provider"} traffic to ${bestComparableProvider?.label ?? bestComparableProvider?.id} for this call profile and keep the current path as fallback.` : bestProviders.length > 0 ? `Use the fastest proven provider mix for this call profile: ${formatProviderMix(bestProviders)}.` : "Collect provider-specific sustained samples before making provider-specific routing decisions." : withinBudget(maxProviderP95Ms, budgets.maxProviderP95Ms) ? "Keep the current provider route for latency-sensitive turns and keep collecting sustained proof." : "Route latency-sensitive turns to a faster provider profile or tighten fallback/circuit-breaker budgets before promotion.",
1745
+ providerId: providerSwitchRecommended ? bestComparableProvider?.id : bestProviders.length === 1 ? bestProviders[0]?.id : undefined,
1746
+ recommendation: providers.length > 0 ? providerSwitchRecommended ? `Switch latency-sensitive ${currentProvider?.role ?? "provider"} routing to ${bestComparableProvider?.label ?? bestComparableProvider?.id}` : bestProviders.length > 0 ? "Prefer the fastest proven provider mix for this call profile" : "Collect provider-specific latency samples" : withinBudget(maxProviderP95Ms, budgets.maxProviderP95Ms) ? "Keep current provider path" : "Change provider routing for latency-sensitive traffic",
1747
+ role: bestComparableProvider?.role,
1748
+ status: providers.length > 0 ? providerSwitchRecommended ? "warn" : bestProviders.length > 0 ? "pass" : bestProvider?.status ?? "fail" : withinBudget(maxProviderP95Ms, budgets.maxProviderP95Ms) ? "pass" : maxProviderP95Ms === undefined ? "fail" : "warn",
1638
1749
  surface: "provider-path"
1639
1750
  });
1640
1751
  const runtimePass = withinBudget(runtimeChannel.maxFirstAudioLatencyMs, budgets.maxRuntimeFirstAudioLatencyMs) && withinBudget(runtimeChannel.maxInterruptionP95Ms, budgets.maxRuntimeInterruptionP95Ms) && withinBudget(runtimeChannel.maxJitterMs, budgets.maxRuntimeJitterMs) && withinBudget(runtimeChannel.maxTimestampDriftMs, budgets.maxRuntimeTimestampDriftMs) && withinBudget(runtimeChannel.maxBackpressureEvents, budgets.maxRuntimeBackpressureEvents);
@@ -1677,16 +1788,21 @@ var buildVoiceProofTrendRecommendationReport = (report, options = {}) => {
1677
1788
  });
1678
1789
  const status = issues.length > 0 ? "fail" : worstRecommendationStatus(recommendations);
1679
1790
  return {
1791
+ bestProvider,
1792
+ bestProviders,
1680
1793
  generatedAt: new Date().toISOString(),
1681
1794
  issues,
1682
1795
  ok: status !== "fail",
1796
+ providers,
1683
1797
  recommendations,
1684
1798
  source: report.source || report.outputDir || report.runId || "proof-trends",
1685
1799
  status,
1686
1800
  summary: {
1687
- keepCurrentProviderPath: recommendations.find((item) => item.surface === "provider-path")?.status === "pass",
1801
+ keepCurrentProviderPath: !providerSwitchRecommended && recommendations.find((item) => item.surface === "provider-path")?.status !== "fail",
1688
1802
  keepCurrentRuntimeChannel: recommendations.find((item) => item.surface === "runtime-channel")?.status === "pass",
1689
- recommendedActions: recommendations.filter((item) => item.status !== "pass").length
1803
+ providerComparisonCount: providers.length,
1804
+ recommendedActions: recommendations.filter((item) => item.status !== "pass").length,
1805
+ switchRecommended: providerSwitchRecommended
1690
1806
  }
1691
1807
  };
1692
1808
  };
@@ -1697,12 +1813,20 @@ var renderVoiceProofTrendRecommendationMarkdown = (report, title = "Voice Provid
1697
1813
  "",
1698
1814
  `- Status: ${report.status}`,
1699
1815
  `- Source: ${report.source}`,
1816
+ `- Best provider mix: ${formatProviderMix(report.bestProviders)}`,
1817
+ `- Provider comparisons: ${String(report.summary.providerComparisonCount)}`,
1700
1818
  `- Recommended actions: ${String(report.summary.recommendedActions)}`,
1701
1819
  "",
1702
1820
  "| Surface | Status | Recommendation | Next move |",
1703
1821
  "| --- | --- | --- | --- |",
1704
1822
  ...report.recommendations.map((recommendation) => `| ${escapeMarkdown(recommendation.surface)} | ${recommendation.status} | ${escapeMarkdown(recommendation.recommendation)} | ${escapeMarkdown(recommendation.nextMove)} |`),
1705
1823
  "",
1824
+ "## Provider Comparison",
1825
+ "",
1826
+ "| Rank | Provider | Role | Status | P95 | Samples | Next move |",
1827
+ "| ---: | --- | --- | --- | ---: | ---: | --- |",
1828
+ ...report.providers.length ? report.providers.map((provider) => `| ${String(provider.rank)} | ${escapeMarkdown(provider.label ?? provider.id)} | ${escapeMarkdown(provider.role ?? "n/a")} | ${provider.status} | ${provider.p95Ms === undefined ? "n/a" : String(provider.p95Ms)} | ${provider.samples === undefined ? "n/a" : String(provider.samples)} | ${escapeMarkdown(provider.nextMove)} |`) : ["| n/a | n/a | n/a | n/a | n/a | n/a | No provider-specific samples were present. |"],
1829
+ "",
1706
1830
  "## Issues",
1707
1831
  "",
1708
1832
  ...report.issues.length ? report.issues.map((issue) => `- ${issue}`) : ["- None"]
@@ -1711,7 +1835,8 @@ var renderVoiceProofTrendRecommendationMarkdown = (report, title = "Voice Provid
1711
1835
  var renderVoiceProofTrendRecommendationHTML = (report, title = "Voice Provider Runtime Recommendations") => {
1712
1836
  const cards = report.recommendations.map((recommendation) => `<article class="${escapeHtml5(recommendation.status)}"><p class="eyebrow">${escapeHtml5(recommendation.surface)} \xB7 ${escapeHtml5(recommendation.status)}</p><h2>${escapeHtml5(recommendation.recommendation)}</h2><p>${escapeHtml5(recommendation.nextMove)}</p><pre>${escapeHtml5(JSON.stringify(recommendation.evidence, null, 2))}</pre></article>`).join("");
1713
1837
  const issues = report.issues.length === 0 ? "<li>None</li>" : report.issues.map((issue) => `<li>${escapeHtml5(issue)}</li>`).join("");
1714
- return `<!doctype html><html lang="en"><head><meta charset="utf-8" /><meta name="viewport" content="width=device-width,initial-scale=1" /><title>${escapeHtml5(title)}</title><style>body{background:#101418;color:#f7f3e8;font-family:ui-sans-serif,system-ui,sans-serif;margin:0}main{margin:auto;max-width:1120px;padding:32px}.hero,article{background:#17201d;border:1px solid #2e3d36;border-radius:24px;margin-bottom:16px;padding:22px}.hero{background:linear-gradient(135deg,rgba(20,184,166,.18),rgba(245,158,11,.12))}.eyebrow{color:#5eead4;font-weight:900;letter-spacing:.1em;text-transform:uppercase}h1{font-size:clamp(2.2rem,6vw,4.7rem);letter-spacing:-.06em;line-height:.92;margin:.2rem 0 1rem}.summary{display:flex;flex-wrap:wrap;gap:10px}.pill{border:1px solid #42534a;border-radius:999px;padding:8px 12px}.pass{border-color:rgba(34,197,94,.55)}.warn{border-color:rgba(245,158,11,.7)}.fail{border-color:rgba(239,68,68,.75)}pre{background:#0b1110;border-radius:14px;overflow:auto;padding:12px}a{color:#5eead4}</style></head><body><main><section class="hero"><p class="eyebrow">Sustained proof recommendations</p><h1>${escapeHtml5(title)}</h1><p>Generated ${escapeHtml5(report.generatedAt)} from ${escapeHtml5(report.source)}.</p><div class="summary"><span class="pill">Status ${escapeHtml5(report.status)}</span><span class="pill">Provider ${report.summary.keepCurrentProviderPath ? "keep" : "change"}</span><span class="pill">Runtime ${report.summary.keepCurrentRuntimeChannel ? "keep" : "tune"}</span><span class="pill">${String(report.summary.recommendedActions)} action(s)</span></div></section>${cards}<section class="hero"><h2>Issues</h2><ul>${issues}</ul></section></main></body></html>`;
1838
+ const providerRows = report.providers.length === 0 ? "<li>No provider-specific samples were present.</li>" : report.providers.map((provider) => `<li><strong>#${String(provider.rank)} ${escapeHtml5(provider.label ?? provider.id)}</strong><span>${escapeHtml5(provider.role ?? "provider")} \xB7 ${escapeHtml5(provider.status)} \xB7 p95 ${escapeHtml5(provider.p95Ms ?? "n/a")}ms \xB7 ${escapeHtml5(provider.samples ?? "n/a")} sample(s)</span><small>${escapeHtml5(provider.nextMove)}</small></li>`).join("");
1839
+ return `<!doctype html><html lang="en"><head><meta charset="utf-8" /><meta name="viewport" content="width=device-width,initial-scale=1" /><title>${escapeHtml5(title)}</title><style>body{background:#101418;color:#f7f3e8;font-family:ui-sans-serif,system-ui,sans-serif;margin:0}main{margin:auto;max-width:1120px;padding:32px}.hero,article{background:#17201d;border:1px solid #2e3d36;border-radius:24px;margin-bottom:16px;padding:22px}.hero{background:linear-gradient(135deg,rgba(20,184,166,.18),rgba(245,158,11,.12))}.eyebrow{color:#5eead4;font-weight:900;letter-spacing:.1em;text-transform:uppercase}h1{font-size:clamp(2.2rem,6vw,4.7rem);letter-spacing:-.06em;line-height:.92;margin:.2rem 0 1rem}.summary{display:flex;flex-wrap:wrap;gap:10px}.pill{border:1px solid #42534a;border-radius:999px;padding:8px 12px}.pass{border-color:rgba(34,197,94,.55)}.warn{border-color:rgba(245,158,11,.7)}.fail{border-color:rgba(239,68,68,.75)}pre{background:#0b1110;border-radius:14px;overflow:auto;padding:12px}a{color:#5eead4}li{margin:.45rem 0}li span,li small{display:block;color:#c9d3ca}</style></head><body><main><section class="hero"><p class="eyebrow">Sustained proof recommendations</p><h1>${escapeHtml5(title)}</h1><p>Generated ${escapeHtml5(report.generatedAt)} from ${escapeHtml5(report.source)}.</p><div class="summary"><span class="pill">Status ${escapeHtml5(report.status)}</span><span class="pill">Provider ${report.summary.keepCurrentProviderPath ? "keep" : "change"}</span><span class="pill">Best mix ${escapeHtml5(formatProviderMix(report.bestProviders))}</span><span class="pill">Runtime ${report.summary.keepCurrentRuntimeChannel ? "keep" : "tune"}</span><span class="pill">${String(report.summary.recommendedActions)} action(s)</span></div></section>${cards}<section class="hero"><h2>Provider Comparison</h2><ul>${providerRows}</ul></section><section class="hero"><h2>Issues</h2><ul>${issues}</ul></section></main></body></html>`;
1715
1840
  };
1716
1841
  var createVoiceProofTrendRecommendationRoutes = (options) => {
1717
1842
  const path = options.path ?? "/api/voice/proof-trend-recommendations";
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@absolutejs/voice",
3
- "version": "0.0.22-beta.334",
3
+ "version": "0.0.22-beta.336",
4
4
  "description": "Voice primitives and Elysia plugin for AbsoluteJS",
5
5
  "repository": {
6
6
  "type": "git",