@absolutejs/voice 0.0.22-beta.335 → 0.0.22-beta.337

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.
@@ -3924,6 +3924,18 @@ var shouldSwitchProvider = (current, best, options) => {
3924
3924
  const improvementRatio = current.p95Ms > 0 ? improvementMs / current.p95Ms : 0;
3925
3925
  return improvementMs >= minImprovementMs || improvementRatio >= minImprovementRatio;
3926
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.label ?? provider.id).toLowerCase().startsWith(provider.role.toLowerCase()) ? `${provider.role.toUpperCase()} ${provider.label ?? provider.id}` : provider.label ?? provider.id).join(", ");
3927
3939
  var evaluateVoiceProofTrendEvidence = (report, input = {}) => {
3928
3940
  const issues = [];
3929
3941
  const requiredStatus = input.requireStatus ?? "pass";
@@ -4040,8 +4052,11 @@ var buildVoiceProofTrendRecommendationReport = (report, options = {}) => {
4040
4052
  const runtimeChannel = readProofTrendRuntimeChannel(report);
4041
4053
  const providers = summarizeProofTrendProviders(report, budgets.maxProviderP95Ms);
4042
4054
  const bestProvider = providers.find((provider) => provider.status === "pass") ?? providers[0];
4055
+ const bestProviders = bestProviderByRole(providers).filter((provider) => provider.status === "pass");
4043
4056
  const currentProvider = options.currentProviderId ? providers.find((provider) => provider.id === options.currentProviderId) : undefined;
4044
- const providerSwitchRecommended = shouldSwitchProvider(currentProvider, bestProvider, options);
4057
+ const hasSingleProviderRole = new Set(bestProviders.map((provider) => provider.role ?? provider.id)).size <= 1;
4058
+ const bestComparableProvider = currentProvider?.role ? bestProviders.find((provider) => provider.role === currentProvider.role) : bestProvider;
4059
+ const providerSwitchRecommended = shouldSwitchProvider(currentProvider, bestComparableProvider, options);
4045
4060
  const recommendations = [];
4046
4061
  const issues = [];
4047
4062
  if (report.ok !== true) {
@@ -4049,19 +4064,20 @@ var buildVoiceProofTrendRecommendationReport = (report, options = {}) => {
4049
4064
  }
4050
4065
  recommendations.push({
4051
4066
  evidence: {
4052
- bestProviderId: bestProvider?.id,
4053
- bestProviderP95Ms: bestProvider?.p95Ms,
4067
+ bestProviderId: currentProvider || hasSingleProviderRole ? bestComparableProvider?.id ?? bestProvider?.id : undefined,
4068
+ bestProviderMix: formatProviderMix(bestProviders),
4069
+ bestProviderP95Ms: currentProvider || hasSingleProviderRole ? bestComparableProvider?.p95Ms ?? bestProvider?.p95Ms : undefined,
4054
4070
  budgetMs: budgets.maxProviderP95Ms,
4055
4071
  currentProviderId: currentProvider?.id ?? options.currentProviderId,
4056
4072
  currentProviderP95Ms: currentProvider?.p95Ms,
4057
4073
  providerComparisonCount: providers.length,
4058
4074
  providerP95Ms: maxProviderP95Ms
4059
4075
  },
4060
- nextMove: providers.length > 0 ? providerSwitchRecommended ? `Route latency-sensitive turns to ${bestProvider?.label ?? bestProvider?.id} for this call profile and keep the current path as fallback.` : bestProvider ? `Use ${bestProvider.label ?? bestProvider.id} as the fastest proven provider path for this call profile and keep collecting sustained comparisons.` : "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.",
4061
- providerId: bestProvider?.id,
4062
- recommendation: providers.length > 0 ? providerSwitchRecommended ? `Switch latency-sensitive routing to ${bestProvider?.label ?? bestProvider?.id}` : bestProvider ? `Prefer ${bestProvider.label ?? bestProvider.id} for this call profile` : "Collect provider-specific latency samples" : withinBudget(maxProviderP95Ms, budgets.maxProviderP95Ms) ? "Keep current provider path" : "Change provider routing for latency-sensitive traffic",
4063
- role: bestProvider?.role,
4064
- status: providers.length > 0 ? providerSwitchRecommended ? "warn" : bestProvider?.status ?? "fail" : withinBudget(maxProviderP95Ms, budgets.maxProviderP95Ms) ? "pass" : maxProviderP95Ms === undefined ? "fail" : "warn",
4076
+ 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.",
4077
+ providerId: providerSwitchRecommended ? bestComparableProvider?.id : bestProviders.length === 1 ? bestProviders[0]?.id : undefined,
4078
+ 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",
4079
+ role: currentProvider || hasSingleProviderRole ? bestComparableProvider?.role : undefined,
4080
+ status: providers.length > 0 ? providerSwitchRecommended ? "warn" : bestProviders.length > 0 ? "pass" : bestProvider?.status ?? "fail" : withinBudget(maxProviderP95Ms, budgets.maxProviderP95Ms) ? "pass" : maxProviderP95Ms === undefined ? "fail" : "warn",
4065
4081
  surface: "provider-path"
4066
4082
  });
4067
4083
  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);
@@ -4105,6 +4121,7 @@ var buildVoiceProofTrendRecommendationReport = (report, options = {}) => {
4105
4121
  const status = issues.length > 0 ? "fail" : worstRecommendationStatus(recommendations);
4106
4122
  return {
4107
4123
  bestProvider,
4124
+ bestProviders,
4108
4125
  generatedAt: new Date().toISOString(),
4109
4126
  issues,
4110
4127
  ok: status !== "fail",
@@ -4128,7 +4145,7 @@ var renderVoiceProofTrendRecommendationMarkdown = (report, title = "Voice Provid
4128
4145
  "",
4129
4146
  `- Status: ${report.status}`,
4130
4147
  `- Source: ${report.source}`,
4131
- `- Best provider: ${report.bestProvider?.label ?? report.bestProvider?.id ?? "n/a"}`,
4148
+ `- Best provider mix: ${formatProviderMix(report.bestProviders)}`,
4132
4149
  `- Provider comparisons: ${String(report.summary.providerComparisonCount)}`,
4133
4150
  `- Recommended actions: ${String(report.summary.recommendedActions)}`,
4134
4151
  "",
@@ -4151,7 +4168,7 @@ var renderVoiceProofTrendRecommendationHTML = (report, title = "Voice Provider R
4151
4168
  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("");
4152
4169
  const issues = report.issues.length === 0 ? "<li>None</li>" : report.issues.map((issue) => `<li>${escapeHtml3(issue)}</li>`).join("");
4153
4170
  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("");
4154
- 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 ${escapeHtml3(report.bestProvider?.label ?? report.bestProvider?.id ?? "n/a")}</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>`;
4171
+ 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>`;
4155
4172
  };
4156
4173
  var createVoiceProofTrendRecommendationRoutes = (options) => {
4157
4174
  const path = options.path ?? "/api/voice/proof-trend-recommendations";
package/dist/index.js CHANGED
@@ -14641,6 +14641,18 @@ var shouldSwitchProvider = (current, best, options) => {
14641
14641
  const improvementRatio = current.p95Ms > 0 ? improvementMs / current.p95Ms : 0;
14642
14642
  return improvementMs >= minImprovementMs || improvementRatio >= minImprovementRatio;
14643
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.label ?? provider.id).toLowerCase().startsWith(provider.role.toLowerCase()) ? `${provider.role.toUpperCase()} ${provider.label ?? provider.id}` : provider.label ?? provider.id).join(", ");
14644
14656
  var evaluateVoiceProofTrendEvidence = (report, input = {}) => {
14645
14657
  const issues = [];
14646
14658
  const requiredStatus = input.requireStatus ?? "pass";
@@ -14757,8 +14769,11 @@ var buildVoiceProofTrendRecommendationReport = (report, options = {}) => {
14757
14769
  const runtimeChannel = readProofTrendRuntimeChannel(report);
14758
14770
  const providers = summarizeProofTrendProviders(report, budgets.maxProviderP95Ms);
14759
14771
  const bestProvider = providers.find((provider) => provider.status === "pass") ?? providers[0];
14772
+ const bestProviders = bestProviderByRole(providers).filter((provider) => provider.status === "pass");
14760
14773
  const currentProvider = options.currentProviderId ? providers.find((provider) => provider.id === options.currentProviderId) : undefined;
14761
- const providerSwitchRecommended = shouldSwitchProvider(currentProvider, bestProvider, options);
14774
+ const hasSingleProviderRole = new Set(bestProviders.map((provider) => provider.role ?? provider.id)).size <= 1;
14775
+ const bestComparableProvider = currentProvider?.role ? bestProviders.find((provider) => provider.role === currentProvider.role) : bestProvider;
14776
+ const providerSwitchRecommended = shouldSwitchProvider(currentProvider, bestComparableProvider, options);
14762
14777
  const recommendations = [];
14763
14778
  const issues = [];
14764
14779
  if (report.ok !== true) {
@@ -14766,19 +14781,20 @@ var buildVoiceProofTrendRecommendationReport = (report, options = {}) => {
14766
14781
  }
14767
14782
  recommendations.push({
14768
14783
  evidence: {
14769
- bestProviderId: bestProvider?.id,
14770
- bestProviderP95Ms: bestProvider?.p95Ms,
14784
+ bestProviderId: currentProvider || hasSingleProviderRole ? bestComparableProvider?.id ?? bestProvider?.id : undefined,
14785
+ bestProviderMix: formatProviderMix(bestProviders),
14786
+ bestProviderP95Ms: currentProvider || hasSingleProviderRole ? bestComparableProvider?.p95Ms ?? bestProvider?.p95Ms : undefined,
14771
14787
  budgetMs: budgets.maxProviderP95Ms,
14772
14788
  currentProviderId: currentProvider?.id ?? options.currentProviderId,
14773
14789
  currentProviderP95Ms: currentProvider?.p95Ms,
14774
14790
  providerComparisonCount: providers.length,
14775
14791
  providerP95Ms: maxProviderP95Ms
14776
14792
  },
14777
- nextMove: providers.length > 0 ? providerSwitchRecommended ? `Route latency-sensitive turns to ${bestProvider?.label ?? bestProvider?.id} for this call profile and keep the current path as fallback.` : bestProvider ? `Use ${bestProvider.label ?? bestProvider.id} as the fastest proven provider path for this call profile and keep collecting sustained comparisons.` : "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.",
14778
- providerId: bestProvider?.id,
14779
- recommendation: providers.length > 0 ? providerSwitchRecommended ? `Switch latency-sensitive routing to ${bestProvider?.label ?? bestProvider?.id}` : bestProvider ? `Prefer ${bestProvider.label ?? bestProvider.id} for this call profile` : "Collect provider-specific latency samples" : withinBudget(maxProviderP95Ms, budgets.maxProviderP95Ms) ? "Keep current provider path" : "Change provider routing for latency-sensitive traffic",
14780
- role: bestProvider?.role,
14781
- status: providers.length > 0 ? providerSwitchRecommended ? "warn" : bestProvider?.status ?? "fail" : withinBudget(maxProviderP95Ms, budgets.maxProviderP95Ms) ? "pass" : maxProviderP95Ms === undefined ? "fail" : "warn",
14793
+ 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.",
14794
+ providerId: providerSwitchRecommended ? bestComparableProvider?.id : bestProviders.length === 1 ? bestProviders[0]?.id : undefined,
14795
+ 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",
14796
+ role: currentProvider || hasSingleProviderRole ? bestComparableProvider?.role : undefined,
14797
+ status: providers.length > 0 ? providerSwitchRecommended ? "warn" : bestProviders.length > 0 ? "pass" : bestProvider?.status ?? "fail" : withinBudget(maxProviderP95Ms, budgets.maxProviderP95Ms) ? "pass" : maxProviderP95Ms === undefined ? "fail" : "warn",
14782
14798
  surface: "provider-path"
14783
14799
  });
14784
14800
  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);
@@ -14822,6 +14838,7 @@ var buildVoiceProofTrendRecommendationReport = (report, options = {}) => {
14822
14838
  const status = issues.length > 0 ? "fail" : worstRecommendationStatus(recommendations);
14823
14839
  return {
14824
14840
  bestProvider,
14841
+ bestProviders,
14825
14842
  generatedAt: new Date().toISOString(),
14826
14843
  issues,
14827
14844
  ok: status !== "fail",
@@ -14845,7 +14862,7 @@ var renderVoiceProofTrendRecommendationMarkdown = (report, title = "Voice Provid
14845
14862
  "",
14846
14863
  `- Status: ${report.status}`,
14847
14864
  `- Source: ${report.source}`,
14848
- `- Best provider: ${report.bestProvider?.label ?? report.bestProvider?.id ?? "n/a"}`,
14865
+ `- Best provider mix: ${formatProviderMix(report.bestProviders)}`,
14849
14866
  `- Provider comparisons: ${String(report.summary.providerComparisonCount)}`,
14850
14867
  `- Recommended actions: ${String(report.summary.recommendedActions)}`,
14851
14868
  "",
@@ -14868,7 +14885,7 @@ var renderVoiceProofTrendRecommendationHTML = (report, title = "Voice Provider R
14868
14885
  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("");
14869
14886
  const issues = report.issues.length === 0 ? "<li>None</li>" : report.issues.map((issue) => `<li>${escapeHtml22(issue)}</li>`).join("");
14870
14887
  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("");
14871
- 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 ${escapeHtml22(report.bestProvider?.label ?? report.bestProvider?.id ?? "n/a")}</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>`;
14888
+ 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>`;
14872
14889
  };
14873
14890
  var createVoiceProofTrendRecommendationRoutes = (options) => {
14874
14891
  const path = options.path ?? "/api/voice/proof-trend-recommendations";
@@ -145,6 +145,7 @@ export type VoiceProofTrendProviderRecommendation = {
145
145
  };
146
146
  export type VoiceProofTrendRecommendationReport = {
147
147
  bestProvider?: VoiceProofTrendProviderRecommendation;
148
+ bestProviders: VoiceProofTrendProviderRecommendation[];
148
149
  generatedAt: string;
149
150
  issues: string[];
150
151
  ok: boolean;
@@ -1672,6 +1672,18 @@ var shouldSwitchProvider = (current, best, options) => {
1672
1672
  const improvementRatio = current.p95Ms > 0 ? improvementMs / current.p95Ms : 0;
1673
1673
  return improvementMs >= minImprovementMs || improvementRatio >= minImprovementRatio;
1674
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.label ?? provider.id).toLowerCase().startsWith(provider.role.toLowerCase()) ? `${provider.role.toUpperCase()} ${provider.label ?? provider.id}` : provider.label ?? provider.id).join(", ");
1675
1687
  var evaluateVoiceProofTrendEvidence = (report, input = {}) => {
1676
1688
  const issues = [];
1677
1689
  const requiredStatus = input.requireStatus ?? "pass";
@@ -1788,8 +1800,11 @@ var buildVoiceProofTrendRecommendationReport = (report, options = {}) => {
1788
1800
  const runtimeChannel = readProofTrendRuntimeChannel(report);
1789
1801
  const providers = summarizeProofTrendProviders(report, budgets.maxProviderP95Ms);
1790
1802
  const bestProvider = providers.find((provider) => provider.status === "pass") ?? providers[0];
1803
+ const bestProviders = bestProviderByRole(providers).filter((provider) => provider.status === "pass");
1791
1804
  const currentProvider = options.currentProviderId ? providers.find((provider) => provider.id === options.currentProviderId) : undefined;
1792
- const providerSwitchRecommended = shouldSwitchProvider(currentProvider, bestProvider, options);
1805
+ const hasSingleProviderRole = new Set(bestProviders.map((provider) => provider.role ?? provider.id)).size <= 1;
1806
+ const bestComparableProvider = currentProvider?.role ? bestProviders.find((provider) => provider.role === currentProvider.role) : bestProvider;
1807
+ const providerSwitchRecommended = shouldSwitchProvider(currentProvider, bestComparableProvider, options);
1793
1808
  const recommendations = [];
1794
1809
  const issues = [];
1795
1810
  if (report.ok !== true) {
@@ -1797,19 +1812,20 @@ var buildVoiceProofTrendRecommendationReport = (report, options = {}) => {
1797
1812
  }
1798
1813
  recommendations.push({
1799
1814
  evidence: {
1800
- bestProviderId: bestProvider?.id,
1801
- bestProviderP95Ms: bestProvider?.p95Ms,
1815
+ bestProviderId: currentProvider || hasSingleProviderRole ? bestComparableProvider?.id ?? bestProvider?.id : undefined,
1816
+ bestProviderMix: formatProviderMix(bestProviders),
1817
+ bestProviderP95Ms: currentProvider || hasSingleProviderRole ? bestComparableProvider?.p95Ms ?? bestProvider?.p95Ms : undefined,
1802
1818
  budgetMs: budgets.maxProviderP95Ms,
1803
1819
  currentProviderId: currentProvider?.id ?? options.currentProviderId,
1804
1820
  currentProviderP95Ms: currentProvider?.p95Ms,
1805
1821
  providerComparisonCount: providers.length,
1806
1822
  providerP95Ms: maxProviderP95Ms
1807
1823
  },
1808
- nextMove: providers.length > 0 ? providerSwitchRecommended ? `Route latency-sensitive turns to ${bestProvider?.label ?? bestProvider?.id} for this call profile and keep the current path as fallback.` : bestProvider ? `Use ${bestProvider.label ?? bestProvider.id} as the fastest proven provider path for this call profile and keep collecting sustained comparisons.` : "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.",
1809
- providerId: bestProvider?.id,
1810
- recommendation: providers.length > 0 ? providerSwitchRecommended ? `Switch latency-sensitive routing to ${bestProvider?.label ?? bestProvider?.id}` : bestProvider ? `Prefer ${bestProvider.label ?? bestProvider.id} for this call profile` : "Collect provider-specific latency samples" : withinBudget(maxProviderP95Ms, budgets.maxProviderP95Ms) ? "Keep current provider path" : "Change provider routing for latency-sensitive traffic",
1811
- role: bestProvider?.role,
1812
- status: providers.length > 0 ? providerSwitchRecommended ? "warn" : bestProvider?.status ?? "fail" : withinBudget(maxProviderP95Ms, budgets.maxProviderP95Ms) ? "pass" : maxProviderP95Ms === undefined ? "fail" : "warn",
1824
+ 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.",
1825
+ providerId: providerSwitchRecommended ? bestComparableProvider?.id : bestProviders.length === 1 ? bestProviders[0]?.id : undefined,
1826
+ 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",
1827
+ role: currentProvider || hasSingleProviderRole ? bestComparableProvider?.role : undefined,
1828
+ status: providers.length > 0 ? providerSwitchRecommended ? "warn" : bestProviders.length > 0 ? "pass" : bestProvider?.status ?? "fail" : withinBudget(maxProviderP95Ms, budgets.maxProviderP95Ms) ? "pass" : maxProviderP95Ms === undefined ? "fail" : "warn",
1813
1829
  surface: "provider-path"
1814
1830
  });
1815
1831
  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);
@@ -1853,6 +1869,7 @@ var buildVoiceProofTrendRecommendationReport = (report, options = {}) => {
1853
1869
  const status = issues.length > 0 ? "fail" : worstRecommendationStatus(recommendations);
1854
1870
  return {
1855
1871
  bestProvider,
1872
+ bestProviders,
1856
1873
  generatedAt: new Date().toISOString(),
1857
1874
  issues,
1858
1875
  ok: status !== "fail",
@@ -1876,7 +1893,7 @@ var renderVoiceProofTrendRecommendationMarkdown = (report, title = "Voice Provid
1876
1893
  "",
1877
1894
  `- Status: ${report.status}`,
1878
1895
  `- Source: ${report.source}`,
1879
- `- Best provider: ${report.bestProvider?.label ?? report.bestProvider?.id ?? "n/a"}`,
1896
+ `- Best provider mix: ${formatProviderMix(report.bestProviders)}`,
1880
1897
  `- Provider comparisons: ${String(report.summary.providerComparisonCount)}`,
1881
1898
  `- Recommended actions: ${String(report.summary.recommendedActions)}`,
1882
1899
  "",
@@ -1899,7 +1916,7 @@ var renderVoiceProofTrendRecommendationHTML = (report, title = "Voice Provider R
1899
1916
  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("");
1900
1917
  const issues = report.issues.length === 0 ? "<li>None</li>" : report.issues.map((issue) => `<li>${escapeHtml5(issue)}</li>`).join("");
1901
1918
  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("");
1902
- 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 ${escapeHtml5(report.bestProvider?.label ?? report.bestProvider?.id ?? "n/a")}</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>`;
1919
+ 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>`;
1903
1920
  };
1904
1921
  var createVoiceProofTrendRecommendationRoutes = (options) => {
1905
1922
  const path = options.path ?? "/api/voice/proof-trend-recommendations";
package/dist/vue/index.js CHANGED
@@ -1593,6 +1593,18 @@ var shouldSwitchProvider = (current, best, options) => {
1593
1593
  const improvementRatio = current.p95Ms > 0 ? improvementMs / current.p95Ms : 0;
1594
1594
  return improvementMs >= minImprovementMs || improvementRatio >= minImprovementRatio;
1595
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.label ?? provider.id).toLowerCase().startsWith(provider.role.toLowerCase()) ? `${provider.role.toUpperCase()} ${provider.label ?? provider.id}` : provider.label ?? provider.id).join(", ");
1596
1608
  var evaluateVoiceProofTrendEvidence = (report, input = {}) => {
1597
1609
  const issues = [];
1598
1610
  const requiredStatus = input.requireStatus ?? "pass";
@@ -1709,8 +1721,11 @@ var buildVoiceProofTrendRecommendationReport = (report, options = {}) => {
1709
1721
  const runtimeChannel = readProofTrendRuntimeChannel(report);
1710
1722
  const providers = summarizeProofTrendProviders(report, budgets.maxProviderP95Ms);
1711
1723
  const bestProvider = providers.find((provider) => provider.status === "pass") ?? providers[0];
1724
+ const bestProviders = bestProviderByRole(providers).filter((provider) => provider.status === "pass");
1712
1725
  const currentProvider = options.currentProviderId ? providers.find((provider) => provider.id === options.currentProviderId) : undefined;
1713
- const providerSwitchRecommended = shouldSwitchProvider(currentProvider, bestProvider, options);
1726
+ const hasSingleProviderRole = new Set(bestProviders.map((provider) => provider.role ?? provider.id)).size <= 1;
1727
+ const bestComparableProvider = currentProvider?.role ? bestProviders.find((provider) => provider.role === currentProvider.role) : bestProvider;
1728
+ const providerSwitchRecommended = shouldSwitchProvider(currentProvider, bestComparableProvider, options);
1714
1729
  const recommendations = [];
1715
1730
  const issues = [];
1716
1731
  if (report.ok !== true) {
@@ -1718,19 +1733,20 @@ var buildVoiceProofTrendRecommendationReport = (report, options = {}) => {
1718
1733
  }
1719
1734
  recommendations.push({
1720
1735
  evidence: {
1721
- bestProviderId: bestProvider?.id,
1722
- bestProviderP95Ms: bestProvider?.p95Ms,
1736
+ bestProviderId: currentProvider || hasSingleProviderRole ? bestComparableProvider?.id ?? bestProvider?.id : undefined,
1737
+ bestProviderMix: formatProviderMix(bestProviders),
1738
+ bestProviderP95Ms: currentProvider || hasSingleProviderRole ? bestComparableProvider?.p95Ms ?? bestProvider?.p95Ms : undefined,
1723
1739
  budgetMs: budgets.maxProviderP95Ms,
1724
1740
  currentProviderId: currentProvider?.id ?? options.currentProviderId,
1725
1741
  currentProviderP95Ms: currentProvider?.p95Ms,
1726
1742
  providerComparisonCount: providers.length,
1727
1743
  providerP95Ms: maxProviderP95Ms
1728
1744
  },
1729
- nextMove: providers.length > 0 ? providerSwitchRecommended ? `Route latency-sensitive turns to ${bestProvider?.label ?? bestProvider?.id} for this call profile and keep the current path as fallback.` : bestProvider ? `Use ${bestProvider.label ?? bestProvider.id} as the fastest proven provider path for this call profile and keep collecting sustained comparisons.` : "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.",
1730
- providerId: bestProvider?.id,
1731
- recommendation: providers.length > 0 ? providerSwitchRecommended ? `Switch latency-sensitive routing to ${bestProvider?.label ?? bestProvider?.id}` : bestProvider ? `Prefer ${bestProvider.label ?? bestProvider.id} for this call profile` : "Collect provider-specific latency samples" : withinBudget(maxProviderP95Ms, budgets.maxProviderP95Ms) ? "Keep current provider path" : "Change provider routing for latency-sensitive traffic",
1732
- role: bestProvider?.role,
1733
- status: providers.length > 0 ? providerSwitchRecommended ? "warn" : bestProvider?.status ?? "fail" : withinBudget(maxProviderP95Ms, budgets.maxProviderP95Ms) ? "pass" : maxProviderP95Ms === undefined ? "fail" : "warn",
1745
+ 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.",
1746
+ providerId: providerSwitchRecommended ? bestComparableProvider?.id : bestProviders.length === 1 ? bestProviders[0]?.id : undefined,
1747
+ 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",
1748
+ role: currentProvider || hasSingleProviderRole ? bestComparableProvider?.role : undefined,
1749
+ status: providers.length > 0 ? providerSwitchRecommended ? "warn" : bestProviders.length > 0 ? "pass" : bestProvider?.status ?? "fail" : withinBudget(maxProviderP95Ms, budgets.maxProviderP95Ms) ? "pass" : maxProviderP95Ms === undefined ? "fail" : "warn",
1734
1750
  surface: "provider-path"
1735
1751
  });
1736
1752
  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);
@@ -1774,6 +1790,7 @@ var buildVoiceProofTrendRecommendationReport = (report, options = {}) => {
1774
1790
  const status = issues.length > 0 ? "fail" : worstRecommendationStatus(recommendations);
1775
1791
  return {
1776
1792
  bestProvider,
1793
+ bestProviders,
1777
1794
  generatedAt: new Date().toISOString(),
1778
1795
  issues,
1779
1796
  ok: status !== "fail",
@@ -1797,7 +1814,7 @@ var renderVoiceProofTrendRecommendationMarkdown = (report, title = "Voice Provid
1797
1814
  "",
1798
1815
  `- Status: ${report.status}`,
1799
1816
  `- Source: ${report.source}`,
1800
- `- Best provider: ${report.bestProvider?.label ?? report.bestProvider?.id ?? "n/a"}`,
1817
+ `- Best provider mix: ${formatProviderMix(report.bestProviders)}`,
1801
1818
  `- Provider comparisons: ${String(report.summary.providerComparisonCount)}`,
1802
1819
  `- Recommended actions: ${String(report.summary.recommendedActions)}`,
1803
1820
  "",
@@ -1820,7 +1837,7 @@ var renderVoiceProofTrendRecommendationHTML = (report, title = "Voice Provider R
1820
1837
  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("");
1821
1838
  const issues = report.issues.length === 0 ? "<li>None</li>" : report.issues.map((issue) => `<li>${escapeHtml5(issue)}</li>`).join("");
1822
1839
  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("");
1823
- 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 ${escapeHtml5(report.bestProvider?.label ?? report.bestProvider?.id ?? "n/a")}</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>`;
1840
+ 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>`;
1824
1841
  };
1825
1842
  var createVoiceProofTrendRecommendationRoutes = (options) => {
1826
1843
  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.335",
3
+ "version": "0.0.22-beta.337",
4
4
  "description": "Voice primitives and Elysia plugin for AbsoluteJS",
5
5
  "repository": {
6
6
  "type": "git",