@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.
- package/dist/client/index.js +132 -7
- package/dist/index.d.ts +1 -1
- package/dist/index.js +132 -7
- package/dist/proofTrends.d.ts +34 -0
- package/dist/react/index.js +132 -7
- package/dist/vue/index.js +132 -7
- package/package.json +1 -1
package/dist/client/index.js
CHANGED
|
@@ -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
|
-
|
|
3968
|
-
|
|
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
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
14685
|
-
|
|
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
|
|
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
|
-
|
|
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
|
-
|
|
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";
|
package/dist/proofTrends.d.ts
CHANGED
|
@@ -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;
|
package/dist/react/index.js
CHANGED
|
@@ -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
|
-
|
|
1716
|
-
|
|
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
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
1637
|
-
|
|
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
|
|
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
|
-
|
|
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
|
-
|
|
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";
|