@absolutejs/voice 0.0.22-beta.334 → 0.0.22-beta.335
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 +116 -7
- package/dist/index.d.ts +1 -1
- package/dist/index.js +116 -7
- package/dist/proofTrends.d.ts +33 -0
- package/dist/react/index.js +116 -7
- package/dist/vue/index.js +116 -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,91 @@ 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
|
+
};
|
|
3842
3927
|
var evaluateVoiceProofTrendEvidence = (report, input = {}) => {
|
|
3843
3928
|
const issues = [];
|
|
3844
3929
|
const requiredStatus = input.requireStatus ?? "pass";
|
|
@@ -3953,6 +4038,10 @@ var buildVoiceProofTrendRecommendationReport = (report, options = {}) => {
|
|
|
3953
4038
|
const maxProviderP95Ms = readProofTrendMaxProviderP95(report);
|
|
3954
4039
|
const maxTurnP95Ms = readProofTrendMaxTurnP95(report);
|
|
3955
4040
|
const runtimeChannel = readProofTrendRuntimeChannel(report);
|
|
4041
|
+
const providers = summarizeProofTrendProviders(report, budgets.maxProviderP95Ms);
|
|
4042
|
+
const bestProvider = providers.find((provider) => provider.status === "pass") ?? providers[0];
|
|
4043
|
+
const currentProvider = options.currentProviderId ? providers.find((provider) => provider.id === options.currentProviderId) : undefined;
|
|
4044
|
+
const providerSwitchRecommended = shouldSwitchProvider(currentProvider, bestProvider, options);
|
|
3956
4045
|
const recommendations = [];
|
|
3957
4046
|
const issues = [];
|
|
3958
4047
|
if (report.ok !== true) {
|
|
@@ -3960,12 +4049,19 @@ var buildVoiceProofTrendRecommendationReport = (report, options = {}) => {
|
|
|
3960
4049
|
}
|
|
3961
4050
|
recommendations.push({
|
|
3962
4051
|
evidence: {
|
|
4052
|
+
bestProviderId: bestProvider?.id,
|
|
4053
|
+
bestProviderP95Ms: bestProvider?.p95Ms,
|
|
3963
4054
|
budgetMs: budgets.maxProviderP95Ms,
|
|
4055
|
+
currentProviderId: currentProvider?.id ?? options.currentProviderId,
|
|
4056
|
+
currentProviderP95Ms: currentProvider?.p95Ms,
|
|
4057
|
+
providerComparisonCount: providers.length,
|
|
3964
4058
|
providerP95Ms: maxProviderP95Ms
|
|
3965
4059
|
},
|
|
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
|
-
|
|
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",
|
|
3969
4065
|
surface: "provider-path"
|
|
3970
4066
|
});
|
|
3971
4067
|
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 +4104,20 @@ var buildVoiceProofTrendRecommendationReport = (report, options = {}) => {
|
|
|
4008
4104
|
});
|
|
4009
4105
|
const status = issues.length > 0 ? "fail" : worstRecommendationStatus(recommendations);
|
|
4010
4106
|
return {
|
|
4107
|
+
bestProvider,
|
|
4011
4108
|
generatedAt: new Date().toISOString(),
|
|
4012
4109
|
issues,
|
|
4013
4110
|
ok: status !== "fail",
|
|
4111
|
+
providers,
|
|
4014
4112
|
recommendations,
|
|
4015
4113
|
source: report.source || report.outputDir || report.runId || "proof-trends",
|
|
4016
4114
|
status,
|
|
4017
4115
|
summary: {
|
|
4018
|
-
keepCurrentProviderPath: recommendations.find((item) => item.surface === "provider-path")?.status
|
|
4116
|
+
keepCurrentProviderPath: !providerSwitchRecommended && recommendations.find((item) => item.surface === "provider-path")?.status !== "fail",
|
|
4019
4117
|
keepCurrentRuntimeChannel: recommendations.find((item) => item.surface === "runtime-channel")?.status === "pass",
|
|
4020
|
-
|
|
4118
|
+
providerComparisonCount: providers.length,
|
|
4119
|
+
recommendedActions: recommendations.filter((item) => item.status !== "pass").length,
|
|
4120
|
+
switchRecommended: providerSwitchRecommended
|
|
4021
4121
|
}
|
|
4022
4122
|
};
|
|
4023
4123
|
};
|
|
@@ -4028,12 +4128,20 @@ var renderVoiceProofTrendRecommendationMarkdown = (report, title = "Voice Provid
|
|
|
4028
4128
|
"",
|
|
4029
4129
|
`- Status: ${report.status}`,
|
|
4030
4130
|
`- Source: ${report.source}`,
|
|
4131
|
+
`- Best provider: ${report.bestProvider?.label ?? report.bestProvider?.id ?? "n/a"}`,
|
|
4132
|
+
`- Provider comparisons: ${String(report.summary.providerComparisonCount)}`,
|
|
4031
4133
|
`- Recommended actions: ${String(report.summary.recommendedActions)}`,
|
|
4032
4134
|
"",
|
|
4033
4135
|
"| Surface | Status | Recommendation | Next move |",
|
|
4034
4136
|
"| --- | --- | --- | --- |",
|
|
4035
4137
|
...report.recommendations.map((recommendation) => `| ${escapeMarkdown(recommendation.surface)} | ${recommendation.status} | ${escapeMarkdown(recommendation.recommendation)} | ${escapeMarkdown(recommendation.nextMove)} |`),
|
|
4036
4138
|
"",
|
|
4139
|
+
"## Provider Comparison",
|
|
4140
|
+
"",
|
|
4141
|
+
"| Rank | Provider | Role | Status | P95 | Samples | Next move |",
|
|
4142
|
+
"| ---: | --- | --- | --- | ---: | ---: | --- |",
|
|
4143
|
+
...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. |"],
|
|
4144
|
+
"",
|
|
4037
4145
|
"## Issues",
|
|
4038
4146
|
"",
|
|
4039
4147
|
...report.issues.length ? report.issues.map((issue) => `- ${issue}`) : ["- None"]
|
|
@@ -4042,7 +4150,8 @@ var renderVoiceProofTrendRecommendationMarkdown = (report, title = "Voice Provid
|
|
|
4042
4150
|
var renderVoiceProofTrendRecommendationHTML = (report, title = "Voice Provider Runtime Recommendations") => {
|
|
4043
4151
|
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
4152
|
const issues = report.issues.length === 0 ? "<li>None</li>" : report.issues.map((issue) => `<li>${escapeHtml3(issue)}</li>`).join("");
|
|
4045
|
-
|
|
4153
|
+
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>`;
|
|
4046
4155
|
};
|
|
4047
4156
|
var createVoiceProofTrendRecommendationRoutes = (options) => {
|
|
4048
4157
|
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,91 @@ 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
|
+
};
|
|
14559
14644
|
var evaluateVoiceProofTrendEvidence = (report, input = {}) => {
|
|
14560
14645
|
const issues = [];
|
|
14561
14646
|
const requiredStatus = input.requireStatus ?? "pass";
|
|
@@ -14670,6 +14755,10 @@ var buildVoiceProofTrendRecommendationReport = (report, options = {}) => {
|
|
|
14670
14755
|
const maxProviderP95Ms = readProofTrendMaxProviderP95(report);
|
|
14671
14756
|
const maxTurnP95Ms = readProofTrendMaxTurnP95(report);
|
|
14672
14757
|
const runtimeChannel = readProofTrendRuntimeChannel(report);
|
|
14758
|
+
const providers = summarizeProofTrendProviders(report, budgets.maxProviderP95Ms);
|
|
14759
|
+
const bestProvider = providers.find((provider) => provider.status === "pass") ?? providers[0];
|
|
14760
|
+
const currentProvider = options.currentProviderId ? providers.find((provider) => provider.id === options.currentProviderId) : undefined;
|
|
14761
|
+
const providerSwitchRecommended = shouldSwitchProvider(currentProvider, bestProvider, options);
|
|
14673
14762
|
const recommendations = [];
|
|
14674
14763
|
const issues = [];
|
|
14675
14764
|
if (report.ok !== true) {
|
|
@@ -14677,12 +14766,19 @@ var buildVoiceProofTrendRecommendationReport = (report, options = {}) => {
|
|
|
14677
14766
|
}
|
|
14678
14767
|
recommendations.push({
|
|
14679
14768
|
evidence: {
|
|
14769
|
+
bestProviderId: bestProvider?.id,
|
|
14770
|
+
bestProviderP95Ms: bestProvider?.p95Ms,
|
|
14680
14771
|
budgetMs: budgets.maxProviderP95Ms,
|
|
14772
|
+
currentProviderId: currentProvider?.id ?? options.currentProviderId,
|
|
14773
|
+
currentProviderP95Ms: currentProvider?.p95Ms,
|
|
14774
|
+
providerComparisonCount: providers.length,
|
|
14681
14775
|
providerP95Ms: maxProviderP95Ms
|
|
14682
14776
|
},
|
|
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
|
-
|
|
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",
|
|
14686
14782
|
surface: "provider-path"
|
|
14687
14783
|
});
|
|
14688
14784
|
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 +14821,20 @@ var buildVoiceProofTrendRecommendationReport = (report, options = {}) => {
|
|
|
14725
14821
|
});
|
|
14726
14822
|
const status = issues.length > 0 ? "fail" : worstRecommendationStatus(recommendations);
|
|
14727
14823
|
return {
|
|
14824
|
+
bestProvider,
|
|
14728
14825
|
generatedAt: new Date().toISOString(),
|
|
14729
14826
|
issues,
|
|
14730
14827
|
ok: status !== "fail",
|
|
14828
|
+
providers,
|
|
14731
14829
|
recommendations,
|
|
14732
14830
|
source: report.source || report.outputDir || report.runId || "proof-trends",
|
|
14733
14831
|
status,
|
|
14734
14832
|
summary: {
|
|
14735
|
-
keepCurrentProviderPath: recommendations.find((item) => item.surface === "provider-path")?.status
|
|
14833
|
+
keepCurrentProviderPath: !providerSwitchRecommended && recommendations.find((item) => item.surface === "provider-path")?.status !== "fail",
|
|
14736
14834
|
keepCurrentRuntimeChannel: recommendations.find((item) => item.surface === "runtime-channel")?.status === "pass",
|
|
14737
|
-
|
|
14835
|
+
providerComparisonCount: providers.length,
|
|
14836
|
+
recommendedActions: recommendations.filter((item) => item.status !== "pass").length,
|
|
14837
|
+
switchRecommended: providerSwitchRecommended
|
|
14738
14838
|
}
|
|
14739
14839
|
};
|
|
14740
14840
|
};
|
|
@@ -14745,12 +14845,20 @@ var renderVoiceProofTrendRecommendationMarkdown = (report, title = "Voice Provid
|
|
|
14745
14845
|
"",
|
|
14746
14846
|
`- Status: ${report.status}`,
|
|
14747
14847
|
`- Source: ${report.source}`,
|
|
14848
|
+
`- Best provider: ${report.bestProvider?.label ?? report.bestProvider?.id ?? "n/a"}`,
|
|
14849
|
+
`- Provider comparisons: ${String(report.summary.providerComparisonCount)}`,
|
|
14748
14850
|
`- Recommended actions: ${String(report.summary.recommendedActions)}`,
|
|
14749
14851
|
"",
|
|
14750
14852
|
"| Surface | Status | Recommendation | Next move |",
|
|
14751
14853
|
"| --- | --- | --- | --- |",
|
|
14752
14854
|
...report.recommendations.map((recommendation) => `| ${escapeMarkdown2(recommendation.surface)} | ${recommendation.status} | ${escapeMarkdown2(recommendation.recommendation)} | ${escapeMarkdown2(recommendation.nextMove)} |`),
|
|
14753
14855
|
"",
|
|
14856
|
+
"## Provider Comparison",
|
|
14857
|
+
"",
|
|
14858
|
+
"| Rank | Provider | Role | Status | P95 | Samples | Next move |",
|
|
14859
|
+
"| ---: | --- | --- | --- | ---: | ---: | --- |",
|
|
14860
|
+
...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. |"],
|
|
14861
|
+
"",
|
|
14754
14862
|
"## Issues",
|
|
14755
14863
|
"",
|
|
14756
14864
|
...report.issues.length ? report.issues.map((issue) => `- ${issue}`) : ["- None"]
|
|
@@ -14759,7 +14867,8 @@ var renderVoiceProofTrendRecommendationMarkdown = (report, title = "Voice Provid
|
|
|
14759
14867
|
var renderVoiceProofTrendRecommendationHTML = (report, title = "Voice Provider Runtime Recommendations") => {
|
|
14760
14868
|
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
14869
|
const issues = report.issues.length === 0 ? "<li>None</li>" : report.issues.map((issue) => `<li>${escapeHtml22(issue)}</li>`).join("");
|
|
14762
|
-
|
|
14870
|
+
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>`;
|
|
14763
14872
|
};
|
|
14764
14873
|
var createVoiceProofTrendRecommendationRoutes = (options) => {
|
|
14765
14874
|
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,43 @@ 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;
|
|
121
148
|
generatedAt: string;
|
|
122
149
|
issues: string[];
|
|
123
150
|
ok: boolean;
|
|
151
|
+
providers: VoiceProofTrendProviderRecommendation[];
|
|
124
152
|
recommendations: VoiceProofTrendRecommendation[];
|
|
125
153
|
source: string;
|
|
126
154
|
status: VoiceProofTrendRecommendationStatus;
|
|
127
155
|
summary: {
|
|
128
156
|
keepCurrentProviderPath: boolean;
|
|
129
157
|
keepCurrentRuntimeChannel: boolean;
|
|
158
|
+
providerComparisonCount: number;
|
|
130
159
|
recommendedActions: number;
|
|
160
|
+
switchRecommended: boolean;
|
|
131
161
|
};
|
|
132
162
|
};
|
|
133
163
|
export type VoiceProofTrendRecommendationOptions = {
|
|
164
|
+
currentProviderId?: string;
|
|
134
165
|
maxLiveP95Ms?: number;
|
|
135
166
|
maxProviderP95Ms?: number;
|
|
136
167
|
maxRuntimeBackpressureEvents?: number;
|
|
@@ -139,6 +170,8 @@ export type VoiceProofTrendRecommendationOptions = {
|
|
|
139
170
|
maxRuntimeJitterMs?: number;
|
|
140
171
|
maxRuntimeTimestampDriftMs?: number;
|
|
141
172
|
maxTurnP95Ms?: number;
|
|
173
|
+
providerSwitchMinImprovementMs?: number;
|
|
174
|
+
providerSwitchMinImprovementRatio?: number;
|
|
142
175
|
};
|
|
143
176
|
export type VoiceProofTrendRecommendationRoutesOptions = VoiceProofTrendRecommendationOptions & {
|
|
144
177
|
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,91 @@ 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
|
+
};
|
|
1590
1675
|
var evaluateVoiceProofTrendEvidence = (report, input = {}) => {
|
|
1591
1676
|
const issues = [];
|
|
1592
1677
|
const requiredStatus = input.requireStatus ?? "pass";
|
|
@@ -1701,6 +1786,10 @@ var buildVoiceProofTrendRecommendationReport = (report, options = {}) => {
|
|
|
1701
1786
|
const maxProviderP95Ms = readProofTrendMaxProviderP95(report);
|
|
1702
1787
|
const maxTurnP95Ms = readProofTrendMaxTurnP95(report);
|
|
1703
1788
|
const runtimeChannel = readProofTrendRuntimeChannel(report);
|
|
1789
|
+
const providers = summarizeProofTrendProviders(report, budgets.maxProviderP95Ms);
|
|
1790
|
+
const bestProvider = providers.find((provider) => provider.status === "pass") ?? providers[0];
|
|
1791
|
+
const currentProvider = options.currentProviderId ? providers.find((provider) => provider.id === options.currentProviderId) : undefined;
|
|
1792
|
+
const providerSwitchRecommended = shouldSwitchProvider(currentProvider, bestProvider, options);
|
|
1704
1793
|
const recommendations = [];
|
|
1705
1794
|
const issues = [];
|
|
1706
1795
|
if (report.ok !== true) {
|
|
@@ -1708,12 +1797,19 @@ var buildVoiceProofTrendRecommendationReport = (report, options = {}) => {
|
|
|
1708
1797
|
}
|
|
1709
1798
|
recommendations.push({
|
|
1710
1799
|
evidence: {
|
|
1800
|
+
bestProviderId: bestProvider?.id,
|
|
1801
|
+
bestProviderP95Ms: bestProvider?.p95Ms,
|
|
1711
1802
|
budgetMs: budgets.maxProviderP95Ms,
|
|
1803
|
+
currentProviderId: currentProvider?.id ?? options.currentProviderId,
|
|
1804
|
+
currentProviderP95Ms: currentProvider?.p95Ms,
|
|
1805
|
+
providerComparisonCount: providers.length,
|
|
1712
1806
|
providerP95Ms: maxProviderP95Ms
|
|
1713
1807
|
},
|
|
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
|
-
|
|
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",
|
|
1717
1813
|
surface: "provider-path"
|
|
1718
1814
|
});
|
|
1719
1815
|
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 +1852,20 @@ var buildVoiceProofTrendRecommendationReport = (report, options = {}) => {
|
|
|
1756
1852
|
});
|
|
1757
1853
|
const status = issues.length > 0 ? "fail" : worstRecommendationStatus(recommendations);
|
|
1758
1854
|
return {
|
|
1855
|
+
bestProvider,
|
|
1759
1856
|
generatedAt: new Date().toISOString(),
|
|
1760
1857
|
issues,
|
|
1761
1858
|
ok: status !== "fail",
|
|
1859
|
+
providers,
|
|
1762
1860
|
recommendations,
|
|
1763
1861
|
source: report.source || report.outputDir || report.runId || "proof-trends",
|
|
1764
1862
|
status,
|
|
1765
1863
|
summary: {
|
|
1766
|
-
keepCurrentProviderPath: recommendations.find((item) => item.surface === "provider-path")?.status
|
|
1864
|
+
keepCurrentProviderPath: !providerSwitchRecommended && recommendations.find((item) => item.surface === "provider-path")?.status !== "fail",
|
|
1767
1865
|
keepCurrentRuntimeChannel: recommendations.find((item) => item.surface === "runtime-channel")?.status === "pass",
|
|
1768
|
-
|
|
1866
|
+
providerComparisonCount: providers.length,
|
|
1867
|
+
recommendedActions: recommendations.filter((item) => item.status !== "pass").length,
|
|
1868
|
+
switchRecommended: providerSwitchRecommended
|
|
1769
1869
|
}
|
|
1770
1870
|
};
|
|
1771
1871
|
};
|
|
@@ -1776,12 +1876,20 @@ var renderVoiceProofTrendRecommendationMarkdown = (report, title = "Voice Provid
|
|
|
1776
1876
|
"",
|
|
1777
1877
|
`- Status: ${report.status}`,
|
|
1778
1878
|
`- Source: ${report.source}`,
|
|
1879
|
+
`- Best provider: ${report.bestProvider?.label ?? report.bestProvider?.id ?? "n/a"}`,
|
|
1880
|
+
`- Provider comparisons: ${String(report.summary.providerComparisonCount)}`,
|
|
1779
1881
|
`- Recommended actions: ${String(report.summary.recommendedActions)}`,
|
|
1780
1882
|
"",
|
|
1781
1883
|
"| Surface | Status | Recommendation | Next move |",
|
|
1782
1884
|
"| --- | --- | --- | --- |",
|
|
1783
1885
|
...report.recommendations.map((recommendation) => `| ${escapeMarkdown(recommendation.surface)} | ${recommendation.status} | ${escapeMarkdown(recommendation.recommendation)} | ${escapeMarkdown(recommendation.nextMove)} |`),
|
|
1784
1886
|
"",
|
|
1887
|
+
"## Provider Comparison",
|
|
1888
|
+
"",
|
|
1889
|
+
"| Rank | Provider | Role | Status | P95 | Samples | Next move |",
|
|
1890
|
+
"| ---: | --- | --- | --- | ---: | ---: | --- |",
|
|
1891
|
+
...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. |"],
|
|
1892
|
+
"",
|
|
1785
1893
|
"## Issues",
|
|
1786
1894
|
"",
|
|
1787
1895
|
...report.issues.length ? report.issues.map((issue) => `- ${issue}`) : ["- None"]
|
|
@@ -1790,7 +1898,8 @@ var renderVoiceProofTrendRecommendationMarkdown = (report, title = "Voice Provid
|
|
|
1790
1898
|
var renderVoiceProofTrendRecommendationHTML = (report, title = "Voice Provider Runtime Recommendations") => {
|
|
1791
1899
|
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
1900
|
const issues = report.issues.length === 0 ? "<li>None</li>" : report.issues.map((issue) => `<li>${escapeHtml5(issue)}</li>`).join("");
|
|
1793
|
-
|
|
1901
|
+
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>`;
|
|
1794
1903
|
};
|
|
1795
1904
|
var createVoiceProofTrendRecommendationRoutes = (options) => {
|
|
1796
1905
|
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,91 @@ 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
|
+
};
|
|
1511
1596
|
var evaluateVoiceProofTrendEvidence = (report, input = {}) => {
|
|
1512
1597
|
const issues = [];
|
|
1513
1598
|
const requiredStatus = input.requireStatus ?? "pass";
|
|
@@ -1622,6 +1707,10 @@ var buildVoiceProofTrendRecommendationReport = (report, options = {}) => {
|
|
|
1622
1707
|
const maxProviderP95Ms = readProofTrendMaxProviderP95(report);
|
|
1623
1708
|
const maxTurnP95Ms = readProofTrendMaxTurnP95(report);
|
|
1624
1709
|
const runtimeChannel = readProofTrendRuntimeChannel(report);
|
|
1710
|
+
const providers = summarizeProofTrendProviders(report, budgets.maxProviderP95Ms);
|
|
1711
|
+
const bestProvider = providers.find((provider) => provider.status === "pass") ?? providers[0];
|
|
1712
|
+
const currentProvider = options.currentProviderId ? providers.find((provider) => provider.id === options.currentProviderId) : undefined;
|
|
1713
|
+
const providerSwitchRecommended = shouldSwitchProvider(currentProvider, bestProvider, options);
|
|
1625
1714
|
const recommendations = [];
|
|
1626
1715
|
const issues = [];
|
|
1627
1716
|
if (report.ok !== true) {
|
|
@@ -1629,12 +1718,19 @@ var buildVoiceProofTrendRecommendationReport = (report, options = {}) => {
|
|
|
1629
1718
|
}
|
|
1630
1719
|
recommendations.push({
|
|
1631
1720
|
evidence: {
|
|
1721
|
+
bestProviderId: bestProvider?.id,
|
|
1722
|
+
bestProviderP95Ms: bestProvider?.p95Ms,
|
|
1632
1723
|
budgetMs: budgets.maxProviderP95Ms,
|
|
1724
|
+
currentProviderId: currentProvider?.id ?? options.currentProviderId,
|
|
1725
|
+
currentProviderP95Ms: currentProvider?.p95Ms,
|
|
1726
|
+
providerComparisonCount: providers.length,
|
|
1633
1727
|
providerP95Ms: maxProviderP95Ms
|
|
1634
1728
|
},
|
|
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
|
-
|
|
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",
|
|
1638
1734
|
surface: "provider-path"
|
|
1639
1735
|
});
|
|
1640
1736
|
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 +1773,20 @@ var buildVoiceProofTrendRecommendationReport = (report, options = {}) => {
|
|
|
1677
1773
|
});
|
|
1678
1774
|
const status = issues.length > 0 ? "fail" : worstRecommendationStatus(recommendations);
|
|
1679
1775
|
return {
|
|
1776
|
+
bestProvider,
|
|
1680
1777
|
generatedAt: new Date().toISOString(),
|
|
1681
1778
|
issues,
|
|
1682
1779
|
ok: status !== "fail",
|
|
1780
|
+
providers,
|
|
1683
1781
|
recommendations,
|
|
1684
1782
|
source: report.source || report.outputDir || report.runId || "proof-trends",
|
|
1685
1783
|
status,
|
|
1686
1784
|
summary: {
|
|
1687
|
-
keepCurrentProviderPath: recommendations.find((item) => item.surface === "provider-path")?.status
|
|
1785
|
+
keepCurrentProviderPath: !providerSwitchRecommended && recommendations.find((item) => item.surface === "provider-path")?.status !== "fail",
|
|
1688
1786
|
keepCurrentRuntimeChannel: recommendations.find((item) => item.surface === "runtime-channel")?.status === "pass",
|
|
1689
|
-
|
|
1787
|
+
providerComparisonCount: providers.length,
|
|
1788
|
+
recommendedActions: recommendations.filter((item) => item.status !== "pass").length,
|
|
1789
|
+
switchRecommended: providerSwitchRecommended
|
|
1690
1790
|
}
|
|
1691
1791
|
};
|
|
1692
1792
|
};
|
|
@@ -1697,12 +1797,20 @@ var renderVoiceProofTrendRecommendationMarkdown = (report, title = "Voice Provid
|
|
|
1697
1797
|
"",
|
|
1698
1798
|
`- Status: ${report.status}`,
|
|
1699
1799
|
`- Source: ${report.source}`,
|
|
1800
|
+
`- Best provider: ${report.bestProvider?.label ?? report.bestProvider?.id ?? "n/a"}`,
|
|
1801
|
+
`- Provider comparisons: ${String(report.summary.providerComparisonCount)}`,
|
|
1700
1802
|
`- Recommended actions: ${String(report.summary.recommendedActions)}`,
|
|
1701
1803
|
"",
|
|
1702
1804
|
"| Surface | Status | Recommendation | Next move |",
|
|
1703
1805
|
"| --- | --- | --- | --- |",
|
|
1704
1806
|
...report.recommendations.map((recommendation) => `| ${escapeMarkdown(recommendation.surface)} | ${recommendation.status} | ${escapeMarkdown(recommendation.recommendation)} | ${escapeMarkdown(recommendation.nextMove)} |`),
|
|
1705
1807
|
"",
|
|
1808
|
+
"## Provider Comparison",
|
|
1809
|
+
"",
|
|
1810
|
+
"| Rank | Provider | Role | Status | P95 | Samples | Next move |",
|
|
1811
|
+
"| ---: | --- | --- | --- | ---: | ---: | --- |",
|
|
1812
|
+
...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. |"],
|
|
1813
|
+
"",
|
|
1706
1814
|
"## Issues",
|
|
1707
1815
|
"",
|
|
1708
1816
|
...report.issues.length ? report.issues.map((issue) => `- ${issue}`) : ["- None"]
|
|
@@ -1711,7 +1819,8 @@ var renderVoiceProofTrendRecommendationMarkdown = (report, title = "Voice Provid
|
|
|
1711
1819
|
var renderVoiceProofTrendRecommendationHTML = (report, title = "Voice Provider Runtime Recommendations") => {
|
|
1712
1820
|
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
1821
|
const issues = report.issues.length === 0 ? "<li>None</li>" : report.issues.map((issue) => `<li>${escapeHtml5(issue)}</li>`).join("");
|
|
1714
|
-
|
|
1822
|
+
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>`;
|
|
1715
1824
|
};
|
|
1716
1825
|
var createVoiceProofTrendRecommendationRoutes = (options) => {
|
|
1717
1826
|
const path = options.path ?? "/api/voice/proof-trend-recommendations";
|