@absolutejs/voice 0.0.22-beta.383 → 0.0.22-beta.385
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 +139 -8
- package/dist/index.d.ts +2 -2
- package/dist/index.js +140 -8
- package/dist/proofTrends.d.ts +42 -1
- package/dist/react/index.js +139 -8
- package/dist/vue/index.js +139 -8
- package/package.json +1 -1
package/dist/client/index.js
CHANGED
|
@@ -4686,6 +4686,136 @@ var appendRealCallRecoveryActionQuery = (href, query) => {
|
|
|
4686
4686
|
const search = new URLSearchParams(entries).toString();
|
|
4687
4687
|
return `${base}${separator}${search}${hash ? `#${hash}` : ""}`;
|
|
4688
4688
|
};
|
|
4689
|
+
var sleepVoiceRealCallProfileRecoveryLoop = (ms) => new Promise((resolve) => setTimeout(resolve, ms));
|
|
4690
|
+
var describeVoiceRealCallProfileRecoveryLoopAction = (action) => [
|
|
4691
|
+
action.label ?? action.id ?? "recovery action",
|
|
4692
|
+
action.profileId ? `profile=${action.profileId}` : undefined,
|
|
4693
|
+
action.href
|
|
4694
|
+
].filter(Boolean).join(" ");
|
|
4695
|
+
var defaultVoiceRealCallProfileRecoveryLoopActionFilter = (action, readinessCheckLabel) => action.method?.toUpperCase() === "POST" && action.sourceCheckLabel === readinessCheckLabel && typeof action.href === "string" && action.href.length > 0;
|
|
4696
|
+
var uniqueVoiceRealCallProfileRecoveryLoopActions = (actions) => {
|
|
4697
|
+
const seen = new Set;
|
|
4698
|
+
return actions.filter((action) => {
|
|
4699
|
+
const key = `${action.method?.toUpperCase() ?? "GET"} ${action.href ?? ""}`;
|
|
4700
|
+
if (seen.has(key)) {
|
|
4701
|
+
return false;
|
|
4702
|
+
}
|
|
4703
|
+
seen.add(key);
|
|
4704
|
+
return true;
|
|
4705
|
+
});
|
|
4706
|
+
};
|
|
4707
|
+
var runVoiceRealCallProfileRecoveryLoop = async (options) => {
|
|
4708
|
+
const baseUrl = options.baseUrl.replace(/\/$/, "");
|
|
4709
|
+
const requestTimeoutMs = options.requestTimeoutMs ?? 5000;
|
|
4710
|
+
const jobPollMs = options.jobPollMs ?? 1200;
|
|
4711
|
+
const jobTimeoutMs = options.jobTimeoutMs ?? 600000;
|
|
4712
|
+
const readinessCheckLabel = options.readinessCheckLabel ?? "Real-call profile history";
|
|
4713
|
+
const fetchImpl = options.fetch ?? fetch;
|
|
4714
|
+
const recoveryActionsHref = options.recoveryActionsHref ?? "/api/production-readiness/recovery-actions";
|
|
4715
|
+
const readinessHref = options.readinessHref ?? "/api/production-readiness";
|
|
4716
|
+
const refreshHref = options.refreshHref === undefined ? "/api/voice/real-call-profile-history/refresh" : options.refreshHref;
|
|
4717
|
+
const jobHref = options.jobHref ?? "/api/voice/real-call-profile-history/actions";
|
|
4718
|
+
const toAbsoluteUrl = (href) => new URL(href, baseUrl).toString();
|
|
4719
|
+
const parseJson = async (response) => {
|
|
4720
|
+
const text = await response.text();
|
|
4721
|
+
try {
|
|
4722
|
+
return JSON.parse(text);
|
|
4723
|
+
} catch (error) {
|
|
4724
|
+
throw new Error(`Expected JSON from ${response.url}, got: ${text.slice(0, 300)}`, { cause: error });
|
|
4725
|
+
}
|
|
4726
|
+
};
|
|
4727
|
+
const fetchJson = async (href, init) => {
|
|
4728
|
+
const response = await fetchImpl(toAbsoluteUrl(href), {
|
|
4729
|
+
headers: { accept: "application/json", ...init?.headers },
|
|
4730
|
+
...init,
|
|
4731
|
+
signal: init?.signal ?? AbortSignal.timeout(requestTimeoutMs)
|
|
4732
|
+
});
|
|
4733
|
+
if (!response.ok) {
|
|
4734
|
+
throw new Error(`${href} returned HTTP ${String(response.status)}.`);
|
|
4735
|
+
}
|
|
4736
|
+
return parseJson(response);
|
|
4737
|
+
};
|
|
4738
|
+
const resolveJobHref = (jobId) => typeof jobHref === "function" ? jobHref(jobId) : `${jobHref.replace(/\/$/, "")}/${jobId}`;
|
|
4739
|
+
const getGate = async (fresh = false) => {
|
|
4740
|
+
const href = fresh ? `${readinessHref}${readinessHref.includes("?") ? "&" : "?"}voiceRecoveryLoopFresh=${String(Date.now())}` : readinessHref;
|
|
4741
|
+
const readiness = await fetchJson(href);
|
|
4742
|
+
return readiness.checks?.find((check) => check.label === readinessCheckLabel) ?? null;
|
|
4743
|
+
};
|
|
4744
|
+
const actionsResponse = await fetchJson(recoveryActionsHref);
|
|
4745
|
+
const actionFilter = options.actionFilter ?? ((action) => defaultVoiceRealCallProfileRecoveryLoopActionFilter(action, readinessCheckLabel));
|
|
4746
|
+
const actions = uniqueVoiceRealCallProfileRecoveryLoopActions((actionsResponse.actions ?? []).filter(actionFilter));
|
|
4747
|
+
if (actions.length === 0) {
|
|
4748
|
+
const realCallProfileGate2 = await getGate();
|
|
4749
|
+
return {
|
|
4750
|
+
actionCount: 0,
|
|
4751
|
+
actions,
|
|
4752
|
+
jobs: [],
|
|
4753
|
+
ok: realCallProfileGate2?.status === "pass",
|
|
4754
|
+
realCallProfileGate: realCallProfileGate2,
|
|
4755
|
+
startFailures: []
|
|
4756
|
+
};
|
|
4757
|
+
}
|
|
4758
|
+
options.logger?.log(`Running ${String(actions.length)} real-call profile recovery action(s) in parallel.`);
|
|
4759
|
+
for (const action of actions) {
|
|
4760
|
+
options.logger?.log(`- ${describeVoiceRealCallProfileRecoveryLoopAction(action)}`);
|
|
4761
|
+
}
|
|
4762
|
+
const starts = await Promise.allSettled(actions.map(async (action) => {
|
|
4763
|
+
if (!action.href) {
|
|
4764
|
+
throw new Error("Recovery action is missing href.");
|
|
4765
|
+
}
|
|
4766
|
+
const body = await fetchJson(action.href, { method: "POST" });
|
|
4767
|
+
return { action, ...body };
|
|
4768
|
+
}));
|
|
4769
|
+
const startedJobs = starts.flatMap((result) => {
|
|
4770
|
+
if (result.status === "rejected") {
|
|
4771
|
+
return [];
|
|
4772
|
+
}
|
|
4773
|
+
return result.value.jobId ? [result.value] : [];
|
|
4774
|
+
});
|
|
4775
|
+
const startFailures = starts.flatMap((result, index) => result.status === "rejected" ? [
|
|
4776
|
+
{
|
|
4777
|
+
action: describeVoiceRealCallProfileRecoveryLoopAction(actions[index] ?? {}),
|
|
4778
|
+
error: result.reason instanceof Error ? result.reason.message : String(result.reason)
|
|
4779
|
+
}
|
|
4780
|
+
] : []);
|
|
4781
|
+
const pollJob = async (jobId) => {
|
|
4782
|
+
const deadline = Date.now() + jobTimeoutMs;
|
|
4783
|
+
while (Date.now() < deadline) {
|
|
4784
|
+
const body = await fetchJson(resolveJobHref(jobId));
|
|
4785
|
+
const job = body.job;
|
|
4786
|
+
if (!job) {
|
|
4787
|
+
throw new Error(`Recovery job ${jobId} was not found.`);
|
|
4788
|
+
}
|
|
4789
|
+
if (job.status === "pass" || job.status === "fail") {
|
|
4790
|
+
return job;
|
|
4791
|
+
}
|
|
4792
|
+
await sleepVoiceRealCallProfileRecoveryLoop(jobPollMs);
|
|
4793
|
+
}
|
|
4794
|
+
throw new Error(`Timed out waiting ${String(jobTimeoutMs)}ms for recovery job ${jobId}.`);
|
|
4795
|
+
};
|
|
4796
|
+
options.logger?.log(`Polling ${String(startedJobs.length)} recovery job(s) in parallel.`);
|
|
4797
|
+
const jobResults = await Promise.allSettled(startedJobs.map((start) => pollJob(start.jobId)));
|
|
4798
|
+
const jobs = jobResults.map((result, index) => ({
|
|
4799
|
+
action: describeVoiceRealCallProfileRecoveryLoopAction(startedJobs[index]?.action ?? {}),
|
|
4800
|
+
jobId: startedJobs[index]?.jobId,
|
|
4801
|
+
result: result.status === "fulfilled" ? result.value : {
|
|
4802
|
+
error: result.reason instanceof Error ? result.reason.message : String(result.reason),
|
|
4803
|
+
status: "fail"
|
|
4804
|
+
}
|
|
4805
|
+
}));
|
|
4806
|
+
if (refreshHref !== false) {
|
|
4807
|
+
await fetchJson(refreshHref, { method: "POST" });
|
|
4808
|
+
}
|
|
4809
|
+
const realCallProfileGate = await getGate(true);
|
|
4810
|
+
return {
|
|
4811
|
+
actionCount: actions.length,
|
|
4812
|
+
actions,
|
|
4813
|
+
jobs,
|
|
4814
|
+
ok: startFailures.length === 0 && jobs.every((job) => job.result.status === "pass") && realCallProfileGate?.status === "pass",
|
|
4815
|
+
realCallProfileGate,
|
|
4816
|
+
startFailures
|
|
4817
|
+
};
|
|
4818
|
+
};
|
|
4689
4819
|
var buildVoiceRealCallProfileRecoveryActions = (report, options = {}) => {
|
|
4690
4820
|
const actions = [
|
|
4691
4821
|
{
|
|
@@ -5021,21 +5151,22 @@ var buildVoiceRealCallProfileHistoryReport = (options = {}) => {
|
|
|
5021
5151
|
];
|
|
5022
5152
|
const passingHistory = history.filter((report) => report.ok === true);
|
|
5023
5153
|
const recommendationHistory = passingHistory.length > 0 ? passingHistory : history;
|
|
5024
|
-
const
|
|
5154
|
+
const profileHistory = history.length > 0 ? history : recommendationHistory;
|
|
5155
|
+
const profiles = buildVoiceProofTrendProfileSummaries(profileHistory, options);
|
|
5025
5156
|
const summary = {
|
|
5026
|
-
cycles:
|
|
5157
|
+
cycles: profileHistory.reduce((total, report) => total + (report.summary.cycles ?? report.cycles.length), 0),
|
|
5027
5158
|
failedReports: history.filter((report) => report.ok !== true).length,
|
|
5028
|
-
maxLiveP95Ms: maxNumber(
|
|
5029
|
-
maxProviderP95Ms: maxNumber(
|
|
5030
|
-
maxTurnP95Ms: maxNumber(
|
|
5159
|
+
maxLiveP95Ms: maxNumber(profileHistory.map(readProofTrendMaxLiveP95)),
|
|
5160
|
+
maxProviderP95Ms: maxNumber(profileHistory.map(readProofTrendMaxProviderP95)),
|
|
5161
|
+
maxTurnP95Ms: maxNumber(profileHistory.map(readProofTrendMaxTurnP95)),
|
|
5031
5162
|
profileCount: profiles.length,
|
|
5032
5163
|
profiles,
|
|
5033
|
-
providers: readProofTrendProviders(
|
|
5034
|
-
runtimeChannel: aggregateProofTrendRuntimeChannel(
|
|
5164
|
+
providers: readProofTrendProviders(profileHistory),
|
|
5165
|
+
runtimeChannel: aggregateProofTrendRuntimeChannel(profileHistory.map(readProofTrendRuntimeChannel).filter((channel) => channel !== undefined))
|
|
5035
5166
|
};
|
|
5036
5167
|
const trend = buildVoiceProofTrendReport({
|
|
5037
5168
|
baseUrl: options.baseUrl,
|
|
5038
|
-
cycles: flattenProofTrendCycles(
|
|
5169
|
+
cycles: flattenProofTrendCycles(profileHistory),
|
|
5039
5170
|
generatedAt,
|
|
5040
5171
|
maxAgeMs: options.maxAgeMs,
|
|
5041
5172
|
now: options.now,
|
package/dist/index.d.ts
CHANGED
|
@@ -30,12 +30,12 @@ export { assertVoicePlatformCoverage, buildVoicePlatformCoverageSummary, createV
|
|
|
30
30
|
export { assertVoiceCompetitiveCoverage, buildVoiceCompetitiveCoverageReport, createVoiceCompetitiveCoverageRoutes, evaluateVoiceCompetitiveCoverage, renderVoiceCompetitiveCoverageHTML, renderVoiceCompetitiveCoverageMarkdown } from './competitiveCoverage';
|
|
31
31
|
export type { VoiceCompetitiveCoverageAssertionInput, VoiceCompetitiveCoverageAssertionReport, VoiceCompetitiveCoverageIssue, VoiceCompetitiveCoverageLevel, VoiceCompetitiveCoverageReport, VoiceCompetitiveCoverageReportInput, VoiceCompetitiveCoverageRoutesOptions, VoiceCompetitiveCoverageStatus, VoiceCompetitiveCoverageSummary, VoiceCompetitiveDepthLevel, VoiceCompetitiveEvidence, VoiceCompetitiveSurface } from './competitiveCoverage';
|
|
32
32
|
export type { VoicePlatformCoverageAssertionInput, VoicePlatformCoverageAssertionReport, VoicePlatformCoverageEvidence, VoicePlatformCoverageRoutesOptions, VoicePlatformCoverageStatus, VoicePlatformCoverageSummary, VoicePlatformCoverageSummaryInput, VoicePlatformCoverageSurface } from './platformCoverage';
|
|
33
|
-
export { assertVoiceProofTrendEvidence, buildEmptyVoiceProofTrendReport, buildVoiceProofTrendProfileSummaries, buildVoiceProofTrendRecommendationReport, buildVoiceProofTrendReportFromRealCallProfiles, buildVoiceProofTrendReport, buildVoiceRealCallProfileEvidenceFromTraceEvents, buildVoiceRealCallProfileDefaults, buildVoiceRealCallProfileHistoryReport, buildVoiceRealCallProfileReadinessCheck, buildVoiceRealCallProfileRecoveryJobHistoryCheck, buildVoiceRealCallProfileRecoveryActions, createVoiceInMemoryRealCallProfileRecoveryJobStore, createVoiceRealCallProfileTraceCollector, createVoiceSQLiteRealCallProfileRecoveryJobStore, createVoiceProofTrendRecommendationRoutes, createVoiceProofTrendRoutes, createVoiceRealCallProfileHistoryRoutes, createVoiceRealCallProfileRecoveryActionRoutes, DEFAULT_VOICE_PROOF_TREND_PROFILE_DEFINITIONS, DEFAULT_VOICE_PROOF_TRENDS_MAX_AGE_MS, evaluateVoiceProofTrendEvidence, formatVoiceProofTrendAge, loadVoiceRealCallProfileEvidenceFromTraceStore, normalizeVoiceProofTrendReport, readVoiceProofTrendReportFile, renderVoiceProofTrendRecommendationHTML, renderVoiceProofTrendRecommendationMarkdown, renderVoiceRealCallProfileHistoryHTML, renderVoiceRealCallProfileHistoryMarkdown, resolveVoiceRealCallProfileProviderRoute } from './proofTrends';
|
|
33
|
+
export { assertVoiceProofTrendEvidence, buildEmptyVoiceProofTrendReport, buildVoiceProofTrendProfileSummaries, buildVoiceProofTrendRecommendationReport, buildVoiceProofTrendReportFromRealCallProfiles, buildVoiceProofTrendReport, buildVoiceRealCallProfileEvidenceFromTraceEvents, buildVoiceRealCallProfileDefaults, buildVoiceRealCallProfileHistoryReport, buildVoiceRealCallProfileReadinessCheck, buildVoiceRealCallProfileRecoveryJobHistoryCheck, buildVoiceRealCallProfileRecoveryActions, createVoiceInMemoryRealCallProfileRecoveryJobStore, createVoiceRealCallProfileTraceCollector, createVoiceSQLiteRealCallProfileRecoveryJobStore, createVoiceProofTrendRecommendationRoutes, createVoiceProofTrendRoutes, createVoiceRealCallProfileHistoryRoutes, createVoiceRealCallProfileRecoveryActionRoutes, DEFAULT_VOICE_PROOF_TREND_PROFILE_DEFINITIONS, DEFAULT_VOICE_PROOF_TRENDS_MAX_AGE_MS, evaluateVoiceProofTrendEvidence, formatVoiceProofTrendAge, loadVoiceRealCallProfileEvidenceFromTraceStore, normalizeVoiceProofTrendReport, readVoiceProofTrendReportFile, renderVoiceProofTrendRecommendationHTML, renderVoiceProofTrendRecommendationMarkdown, renderVoiceRealCallProfileHistoryHTML, renderVoiceRealCallProfileHistoryMarkdown, runVoiceRealCallProfileRecoveryLoop, resolveVoiceRealCallProfileProviderRoute } from './proofTrends';
|
|
34
34
|
export { applyVoiceProfileSwitchGuard, buildVoiceProfileSwitchReadinessReport, buildVoiceProfileSwitchLiveDecisionReport, createVoiceProfileSwitchLiveDecisionRoutes, createVoiceProfileSwitchPolicyProofRoutes, createVoiceProfileSwitchReadinessRoutes, recommendVoiceProfileSwitch, renderVoiceProfileSwitchLiveDecisionHTML, renderVoiceProfileSwitchPolicyProofHTML, renderVoiceProfileSwitchReadinessHTML, runVoiceProfileSwitchPolicyProof } from './profileSwitchRecommendation';
|
|
35
35
|
export type { VoiceProfileSwitchGuardAction, VoiceProfileSwitchGuardDecision, VoiceProfileSwitchGuardMode, VoiceProfileSwitchGuardOptions, VoiceProfileSwitchObservedSignals, VoiceProfileSwitchLiveDecisionEvidence, VoiceProfileSwitchLiveDecisionReport, VoiceProfileSwitchLiveDecisionReportOptions, VoiceProfileSwitchLiveDecisionRoutesOptions, VoiceProfileSwitchLiveDecisionSession, VoiceProfileSwitchPolicyProofCase, VoiceProfileSwitchPolicyProofCaseResult, VoiceProfileSwitchPolicyProofOptions, VoiceProfileSwitchPolicyProofReport, VoiceProfileSwitchPolicyProofRoutesOptions, VoiceProfileSwitchReadinessIssue, VoiceProfileSwitchReadinessOptions, VoiceProfileSwitchReadinessReport, VoiceProfileSwitchReadinessRoutesOptions, VoiceProfileSwitchReadinessStatus, VoiceProfileSwitchRecommendation, VoiceProfileSwitchRecommendationOptions } from './profileSwitchRecommendation';
|
|
36
36
|
export { buildVoiceProviderDecisionTraceReport, createVoiceProviderDecisionTraceEvent, createVoiceProviderDecisionTraceRoutes, listVoiceProviderDecisionTraces, renderVoiceProviderDecisionTraceHTML, renderVoiceProviderDecisionTraceMarkdown } from './providerDecisionTraces';
|
|
37
37
|
export type { VoiceProviderDecisionStatus, VoiceProviderDecisionSurfaceReport, VoiceProviderDecisionTrace, VoiceProviderDecisionTraceInput, VoiceProviderDecisionTraceIssue, VoiceProviderDecisionTraceReport, VoiceProviderDecisionTraceReportOptions, VoiceProviderDecisionTraceRoutesOptions } from './providerDecisionTraces';
|
|
38
|
-
export type { VoiceProofTrendAssertionInput, VoiceProofTrendAssertionReport, VoiceProofTrendCycle, VoiceProofTrendProfileDefinition, VoiceProofTrendProfileRecommendation, VoiceProofTrendProfileSummaryOptions, VoiceProofTrendProfileSummary, VoiceProofTrendProviderRecommendation, VoiceProofTrendProviderSummary, VoiceProofTrendRecommendation, VoiceProofTrendRecommendationOptions, VoiceProofTrendRecommendationReport, VoiceProofTrendRecommendationRoutesOptions, VoiceProofTrendRecommendationStatus, VoiceProofTrendRecommendationSurface, VoiceProofTrendRealCallProfileEvidence, VoiceProofTrendRealCallProfileReportOptions, VoiceProofTrendReport, VoiceProofTrendReportInput, VoiceProofTrendRoutesOptions, VoiceProofTrendRuntimeChannelSummary, VoiceProofTrendStatus, VoiceProofTrendSummary, VoiceRealCallProfileDefault, VoiceRealCallProfileDefaultsOptions, VoiceRealCallProfileDefaultsReport, VoiceRealCallProfileHistoryOptions, VoiceRealCallProfileHistoryReport, VoiceRealCallProfileHistoryRoutesOptions, VoiceRealCallProfileProviderRouteOptions, VoiceRealCallProfileReadinessCheckOptions, VoiceRealCallProfileRecoveryActionOptions, VoiceRealCallProfileRecoveryAction, VoiceRealCallProfileRecoveryActionHandler, VoiceRealCallProfileRecoveryActionHandlerInput, VoiceRealCallProfileRecoveryActionId, VoiceRealCallProfileRecoveryJobHistoryCheckOptions, VoiceRealCallProfileRecoveryActionResult, VoiceRealCallProfileRecoveryActionRoutesOptions, VoiceRealCallProfileRecoveryJob, VoiceRealCallProfileRecoveryJobCreateInput, VoiceRealCallProfileRecoveryJobListOptions, VoiceRealCallProfileRecoveryJobStatus, VoiceRealCallProfileRecoveryJobStore, VoiceRealCallProfileRecoveryJobUpdate, VoiceSQLiteRealCallProfileRecoveryJobStoreOptions, VoiceRealCallProfileTraceCollector, VoiceRealCallProfileTraceCollectorEvidenceOptions, VoiceRealCallProfileTraceCollectorOptions, VoiceRealCallProfileTraceEvidenceOptions, VoiceRealCallProfileTraceStoreEvidenceOptions } from './proofTrends';
|
|
38
|
+
export type { VoiceProofTrendAssertionInput, VoiceProofTrendAssertionReport, VoiceProofTrendCycle, VoiceProofTrendProfileDefinition, VoiceProofTrendProfileRecommendation, VoiceProofTrendProfileSummaryOptions, VoiceProofTrendProfileSummary, VoiceProofTrendProviderRecommendation, VoiceProofTrendProviderSummary, VoiceProofTrendRecommendation, VoiceProofTrendRecommendationOptions, VoiceProofTrendRecommendationReport, VoiceProofTrendRecommendationRoutesOptions, VoiceProofTrendRecommendationStatus, VoiceProofTrendRecommendationSurface, VoiceProofTrendRealCallProfileEvidence, VoiceProofTrendRealCallProfileReportOptions, VoiceProofTrendReport, VoiceProofTrendReportInput, VoiceProofTrendRoutesOptions, VoiceProofTrendRuntimeChannelSummary, VoiceProofTrendStatus, VoiceProofTrendSummary, VoiceRealCallProfileDefault, VoiceRealCallProfileDefaultsOptions, VoiceRealCallProfileDefaultsReport, VoiceRealCallProfileHistoryOptions, VoiceRealCallProfileHistoryReport, VoiceRealCallProfileHistoryRoutesOptions, VoiceRealCallProfileProviderRouteOptions, VoiceRealCallProfileReadinessCheckOptions, VoiceRealCallProfileRecoveryActionOptions, VoiceRealCallProfileRecoveryAction, VoiceRealCallProfileRecoveryActionHandler, VoiceRealCallProfileRecoveryActionHandlerInput, VoiceRealCallProfileRecoveryActionId, VoiceRealCallProfileRecoveryJobHistoryCheckOptions, VoiceRealCallProfileRecoveryActionResult, VoiceRealCallProfileRecoveryActionRoutesOptions, VoiceRealCallProfileRecoveryJob, VoiceRealCallProfileRecoveryJobCreateInput, VoiceRealCallProfileRecoveryJobListOptions, VoiceRealCallProfileRecoveryJobStatus, VoiceRealCallProfileRecoveryJobStore, VoiceRealCallProfileRecoveryJobUpdate, VoiceRealCallProfileRecoveryLoopAction, VoiceRealCallProfileRecoveryLoopJob, VoiceRealCallProfileRecoveryLoopJobResult, VoiceRealCallProfileRecoveryLoopOptions, VoiceRealCallProfileRecoveryLoopReport, VoiceRealCallProfileRecoveryLoopStartFailure, VoiceSQLiteRealCallProfileRecoveryJobStoreOptions, VoiceRealCallProfileTraceCollector, VoiceRealCallProfileTraceCollectorEvidenceOptions, VoiceRealCallProfileTraceCollectorOptions, VoiceRealCallProfileTraceEvidenceOptions, VoiceRealCallProfileTraceStoreEvidenceOptions } from './proofTrends';
|
|
39
39
|
export { assertVoiceSloCalibration, buildVoiceSloCalibrationReport, buildVoiceSloReadinessThresholdReport, createVoiceSloReadinessThresholdOptions, createVoiceSloReadinessThresholdRoutes, createVoiceSloThresholdProfile, createVoiceSloCalibrationRoutes, renderVoiceSloCalibrationMarkdown, renderVoiceSloReadinessThresholdHTML, renderVoiceSloReadinessThresholdMarkdown } from './sloCalibration';
|
|
40
40
|
export type { VoiceSloCalibrationMetricKey, VoiceSloCalibrationOptions, VoiceSloCalibrationReport, VoiceSloCalibrationRoutesOptions, VoiceSloCalibrationSample, VoiceSloCalibrationStatus, VoiceSloCalibrationThreshold, VoiceSloCalibrationThresholds, VoiceSloReadinessThresholdReport, VoiceSloReadinessThresholdReportOptions, VoiceSloReadinessThresholdOptions, VoiceSloReadinessThresholdRoutesOptions, VoiceSloThresholdProfile } from './sloCalibration';
|
|
41
41
|
export { assertVoiceLiveOpsControlEvidence, assertVoiceLiveOpsEvidence, buildVoiceLiveOpsControlState, createVoiceLiveOpsController, createVoiceLiveOpsRoutes, createVoiceMemoryLiveOpsControlStore, evaluateVoiceLiveOpsControlEvidence, evaluateVoiceLiveOpsEvidence, getVoiceLiveOpsControlStatus, VOICE_LIVE_OPS_ACTIONS } from './liveOps';
|
package/dist/index.js
CHANGED
|
@@ -16155,6 +16155,136 @@ var appendRealCallRecoveryActionQuery = (href, query) => {
|
|
|
16155
16155
|
const search = new URLSearchParams(entries).toString();
|
|
16156
16156
|
return `${base}${separator}${search}${hash ? `#${hash}` : ""}`;
|
|
16157
16157
|
};
|
|
16158
|
+
var sleepVoiceRealCallProfileRecoveryLoop = (ms) => new Promise((resolve2) => setTimeout(resolve2, ms));
|
|
16159
|
+
var describeVoiceRealCallProfileRecoveryLoopAction = (action) => [
|
|
16160
|
+
action.label ?? action.id ?? "recovery action",
|
|
16161
|
+
action.profileId ? `profile=${action.profileId}` : undefined,
|
|
16162
|
+
action.href
|
|
16163
|
+
].filter(Boolean).join(" ");
|
|
16164
|
+
var defaultVoiceRealCallProfileRecoveryLoopActionFilter = (action, readinessCheckLabel) => action.method?.toUpperCase() === "POST" && action.sourceCheckLabel === readinessCheckLabel && typeof action.href === "string" && action.href.length > 0;
|
|
16165
|
+
var uniqueVoiceRealCallProfileRecoveryLoopActions = (actions) => {
|
|
16166
|
+
const seen = new Set;
|
|
16167
|
+
return actions.filter((action) => {
|
|
16168
|
+
const key = `${action.method?.toUpperCase() ?? "GET"} ${action.href ?? ""}`;
|
|
16169
|
+
if (seen.has(key)) {
|
|
16170
|
+
return false;
|
|
16171
|
+
}
|
|
16172
|
+
seen.add(key);
|
|
16173
|
+
return true;
|
|
16174
|
+
});
|
|
16175
|
+
};
|
|
16176
|
+
var runVoiceRealCallProfileRecoveryLoop = async (options) => {
|
|
16177
|
+
const baseUrl = options.baseUrl.replace(/\/$/, "");
|
|
16178
|
+
const requestTimeoutMs = options.requestTimeoutMs ?? 5000;
|
|
16179
|
+
const jobPollMs = options.jobPollMs ?? 1200;
|
|
16180
|
+
const jobTimeoutMs = options.jobTimeoutMs ?? 600000;
|
|
16181
|
+
const readinessCheckLabel = options.readinessCheckLabel ?? "Real-call profile history";
|
|
16182
|
+
const fetchImpl = options.fetch ?? fetch;
|
|
16183
|
+
const recoveryActionsHref = options.recoveryActionsHref ?? "/api/production-readiness/recovery-actions";
|
|
16184
|
+
const readinessHref = options.readinessHref ?? "/api/production-readiness";
|
|
16185
|
+
const refreshHref = options.refreshHref === undefined ? "/api/voice/real-call-profile-history/refresh" : options.refreshHref;
|
|
16186
|
+
const jobHref = options.jobHref ?? "/api/voice/real-call-profile-history/actions";
|
|
16187
|
+
const toAbsoluteUrl = (href) => new URL(href, baseUrl).toString();
|
|
16188
|
+
const parseJson = async (response) => {
|
|
16189
|
+
const text = await response.text();
|
|
16190
|
+
try {
|
|
16191
|
+
return JSON.parse(text);
|
|
16192
|
+
} catch (error) {
|
|
16193
|
+
throw new Error(`Expected JSON from ${response.url}, got: ${text.slice(0, 300)}`, { cause: error });
|
|
16194
|
+
}
|
|
16195
|
+
};
|
|
16196
|
+
const fetchJson = async (href, init) => {
|
|
16197
|
+
const response = await fetchImpl(toAbsoluteUrl(href), {
|
|
16198
|
+
headers: { accept: "application/json", ...init?.headers },
|
|
16199
|
+
...init,
|
|
16200
|
+
signal: init?.signal ?? AbortSignal.timeout(requestTimeoutMs)
|
|
16201
|
+
});
|
|
16202
|
+
if (!response.ok) {
|
|
16203
|
+
throw new Error(`${href} returned HTTP ${String(response.status)}.`);
|
|
16204
|
+
}
|
|
16205
|
+
return parseJson(response);
|
|
16206
|
+
};
|
|
16207
|
+
const resolveJobHref = (jobId) => typeof jobHref === "function" ? jobHref(jobId) : `${jobHref.replace(/\/$/, "")}/${jobId}`;
|
|
16208
|
+
const getGate = async (fresh = false) => {
|
|
16209
|
+
const href = fresh ? `${readinessHref}${readinessHref.includes("?") ? "&" : "?"}voiceRecoveryLoopFresh=${String(Date.now())}` : readinessHref;
|
|
16210
|
+
const readiness = await fetchJson(href);
|
|
16211
|
+
return readiness.checks?.find((check) => check.label === readinessCheckLabel) ?? null;
|
|
16212
|
+
};
|
|
16213
|
+
const actionsResponse = await fetchJson(recoveryActionsHref);
|
|
16214
|
+
const actionFilter = options.actionFilter ?? ((action) => defaultVoiceRealCallProfileRecoveryLoopActionFilter(action, readinessCheckLabel));
|
|
16215
|
+
const actions = uniqueVoiceRealCallProfileRecoveryLoopActions((actionsResponse.actions ?? []).filter(actionFilter));
|
|
16216
|
+
if (actions.length === 0) {
|
|
16217
|
+
const realCallProfileGate2 = await getGate();
|
|
16218
|
+
return {
|
|
16219
|
+
actionCount: 0,
|
|
16220
|
+
actions,
|
|
16221
|
+
jobs: [],
|
|
16222
|
+
ok: realCallProfileGate2?.status === "pass",
|
|
16223
|
+
realCallProfileGate: realCallProfileGate2,
|
|
16224
|
+
startFailures: []
|
|
16225
|
+
};
|
|
16226
|
+
}
|
|
16227
|
+
options.logger?.log(`Running ${String(actions.length)} real-call profile recovery action(s) in parallel.`);
|
|
16228
|
+
for (const action of actions) {
|
|
16229
|
+
options.logger?.log(`- ${describeVoiceRealCallProfileRecoveryLoopAction(action)}`);
|
|
16230
|
+
}
|
|
16231
|
+
const starts = await Promise.allSettled(actions.map(async (action) => {
|
|
16232
|
+
if (!action.href) {
|
|
16233
|
+
throw new Error("Recovery action is missing href.");
|
|
16234
|
+
}
|
|
16235
|
+
const body = await fetchJson(action.href, { method: "POST" });
|
|
16236
|
+
return { action, ...body };
|
|
16237
|
+
}));
|
|
16238
|
+
const startedJobs = starts.flatMap((result) => {
|
|
16239
|
+
if (result.status === "rejected") {
|
|
16240
|
+
return [];
|
|
16241
|
+
}
|
|
16242
|
+
return result.value.jobId ? [result.value] : [];
|
|
16243
|
+
});
|
|
16244
|
+
const startFailures = starts.flatMap((result, index) => result.status === "rejected" ? [
|
|
16245
|
+
{
|
|
16246
|
+
action: describeVoiceRealCallProfileRecoveryLoopAction(actions[index] ?? {}),
|
|
16247
|
+
error: result.reason instanceof Error ? result.reason.message : String(result.reason)
|
|
16248
|
+
}
|
|
16249
|
+
] : []);
|
|
16250
|
+
const pollJob = async (jobId) => {
|
|
16251
|
+
const deadline = Date.now() + jobTimeoutMs;
|
|
16252
|
+
while (Date.now() < deadline) {
|
|
16253
|
+
const body = await fetchJson(resolveJobHref(jobId));
|
|
16254
|
+
const job = body.job;
|
|
16255
|
+
if (!job) {
|
|
16256
|
+
throw new Error(`Recovery job ${jobId} was not found.`);
|
|
16257
|
+
}
|
|
16258
|
+
if (job.status === "pass" || job.status === "fail") {
|
|
16259
|
+
return job;
|
|
16260
|
+
}
|
|
16261
|
+
await sleepVoiceRealCallProfileRecoveryLoop(jobPollMs);
|
|
16262
|
+
}
|
|
16263
|
+
throw new Error(`Timed out waiting ${String(jobTimeoutMs)}ms for recovery job ${jobId}.`);
|
|
16264
|
+
};
|
|
16265
|
+
options.logger?.log(`Polling ${String(startedJobs.length)} recovery job(s) in parallel.`);
|
|
16266
|
+
const jobResults = await Promise.allSettled(startedJobs.map((start) => pollJob(start.jobId)));
|
|
16267
|
+
const jobs = jobResults.map((result, index) => ({
|
|
16268
|
+
action: describeVoiceRealCallProfileRecoveryLoopAction(startedJobs[index]?.action ?? {}),
|
|
16269
|
+
jobId: startedJobs[index]?.jobId,
|
|
16270
|
+
result: result.status === "fulfilled" ? result.value : {
|
|
16271
|
+
error: result.reason instanceof Error ? result.reason.message : String(result.reason),
|
|
16272
|
+
status: "fail"
|
|
16273
|
+
}
|
|
16274
|
+
}));
|
|
16275
|
+
if (refreshHref !== false) {
|
|
16276
|
+
await fetchJson(refreshHref, { method: "POST" });
|
|
16277
|
+
}
|
|
16278
|
+
const realCallProfileGate = await getGate(true);
|
|
16279
|
+
return {
|
|
16280
|
+
actionCount: actions.length,
|
|
16281
|
+
actions,
|
|
16282
|
+
jobs,
|
|
16283
|
+
ok: startFailures.length === 0 && jobs.every((job) => job.result.status === "pass") && realCallProfileGate?.status === "pass",
|
|
16284
|
+
realCallProfileGate,
|
|
16285
|
+
startFailures
|
|
16286
|
+
};
|
|
16287
|
+
};
|
|
16158
16288
|
var buildVoiceRealCallProfileRecoveryActions = (report, options = {}) => {
|
|
16159
16289
|
const actions = [
|
|
16160
16290
|
{
|
|
@@ -16490,21 +16620,22 @@ var buildVoiceRealCallProfileHistoryReport = (options = {}) => {
|
|
|
16490
16620
|
];
|
|
16491
16621
|
const passingHistory = history.filter((report) => report.ok === true);
|
|
16492
16622
|
const recommendationHistory = passingHistory.length > 0 ? passingHistory : history;
|
|
16493
|
-
const
|
|
16623
|
+
const profileHistory = history.length > 0 ? history : recommendationHistory;
|
|
16624
|
+
const profiles = buildVoiceProofTrendProfileSummaries(profileHistory, options);
|
|
16494
16625
|
const summary = {
|
|
16495
|
-
cycles:
|
|
16626
|
+
cycles: profileHistory.reduce((total, report) => total + (report.summary.cycles ?? report.cycles.length), 0),
|
|
16496
16627
|
failedReports: history.filter((report) => report.ok !== true).length,
|
|
16497
|
-
maxLiveP95Ms: maxNumber(
|
|
16498
|
-
maxProviderP95Ms: maxNumber(
|
|
16499
|
-
maxTurnP95Ms: maxNumber(
|
|
16628
|
+
maxLiveP95Ms: maxNumber(profileHistory.map(readProofTrendMaxLiveP95)),
|
|
16629
|
+
maxProviderP95Ms: maxNumber(profileHistory.map(readProofTrendMaxProviderP95)),
|
|
16630
|
+
maxTurnP95Ms: maxNumber(profileHistory.map(readProofTrendMaxTurnP95)),
|
|
16500
16631
|
profileCount: profiles.length,
|
|
16501
16632
|
profiles,
|
|
16502
|
-
providers: readProofTrendProviders(
|
|
16503
|
-
runtimeChannel: aggregateProofTrendRuntimeChannel(
|
|
16633
|
+
providers: readProofTrendProviders(profileHistory),
|
|
16634
|
+
runtimeChannel: aggregateProofTrendRuntimeChannel(profileHistory.map(readProofTrendRuntimeChannel).filter((channel) => channel !== undefined))
|
|
16504
16635
|
};
|
|
16505
16636
|
const trend = buildVoiceProofTrendReport({
|
|
16506
16637
|
baseUrl: options.baseUrl,
|
|
16507
|
-
cycles: flattenProofTrendCycles(
|
|
16638
|
+
cycles: flattenProofTrendCycles(profileHistory),
|
|
16508
16639
|
generatedAt,
|
|
16509
16640
|
maxAgeMs: options.maxAgeMs,
|
|
16510
16641
|
now: options.now,
|
|
@@ -39018,6 +39149,7 @@ export {
|
|
|
39018
39149
|
runVoiceScenarioFixtureEvals,
|
|
39019
39150
|
runVoiceScenarioEvals,
|
|
39020
39151
|
runVoiceReconnectContract,
|
|
39152
|
+
runVoiceRealCallProfileRecoveryLoop,
|
|
39021
39153
|
runVoiceProviderRoutingContract,
|
|
39022
39154
|
runVoiceProfileSwitchPolicyProof,
|
|
39023
39155
|
runVoicePhoneAgentProductionSmokeContract,
|
package/dist/proofTrends.d.ts
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import { Elysia } from 'elysia';
|
|
2
2
|
import type { Database } from 'bun:sqlite';
|
|
3
|
-
import type { VoiceProductionReadinessAction, VoiceProductionReadinessCheck } from './productionReadiness';
|
|
3
|
+
import type { VoiceProductionReadinessAction, VoiceProductionReadinessCheck, VoiceReadinessRecoveryAction } from './productionReadiness';
|
|
4
4
|
import type { StoredVoiceTraceEvent, VoiceTraceEventStore } from './trace';
|
|
5
5
|
export type VoiceProofTrendStatus = 'empty' | 'fail' | 'pass' | 'stale';
|
|
6
6
|
export type VoiceProofTrendSummary = {
|
|
@@ -469,6 +469,46 @@ export type VoiceRealCallProfileRecoveryActionRoutesOptions = VoiceRealCallProfi
|
|
|
469
469
|
handlers?: Partial<Record<VoiceRealCallProfileRecoveryActionId, VoiceRealCallProfileRecoveryActionHandler>>;
|
|
470
470
|
jobStore?: VoiceRealCallProfileRecoveryJobStore;
|
|
471
471
|
};
|
|
472
|
+
export type VoiceRealCallProfileRecoveryLoopAction = Partial<VoiceReadinessRecoveryAction> & Partial<VoiceRealCallProfileRecoveryAction> & {
|
|
473
|
+
href?: string;
|
|
474
|
+
method?: string;
|
|
475
|
+
};
|
|
476
|
+
export type VoiceRealCallProfileRecoveryLoopJob = Partial<VoiceRealCallProfileRecoveryJob> & {
|
|
477
|
+
error?: string;
|
|
478
|
+
id?: string;
|
|
479
|
+
status?: string;
|
|
480
|
+
};
|
|
481
|
+
export type VoiceRealCallProfileRecoveryLoopJobResult = {
|
|
482
|
+
action: string;
|
|
483
|
+
jobId?: string;
|
|
484
|
+
result: VoiceRealCallProfileRecoveryLoopJob;
|
|
485
|
+
};
|
|
486
|
+
export type VoiceRealCallProfileRecoveryLoopStartFailure = {
|
|
487
|
+
action: string;
|
|
488
|
+
error: string;
|
|
489
|
+
};
|
|
490
|
+
export type VoiceRealCallProfileRecoveryLoopReport = {
|
|
491
|
+
actionCount: number;
|
|
492
|
+
actions: VoiceRealCallProfileRecoveryLoopAction[];
|
|
493
|
+
jobs: VoiceRealCallProfileRecoveryLoopJobResult[];
|
|
494
|
+
ok: boolean;
|
|
495
|
+
realCallProfileGate: VoiceProductionReadinessCheck | null;
|
|
496
|
+
startFailures: VoiceRealCallProfileRecoveryLoopStartFailure[];
|
|
497
|
+
};
|
|
498
|
+
export type VoiceRealCallProfileRecoveryLoopOptions = {
|
|
499
|
+
actionFilter?: (action: VoiceRealCallProfileRecoveryLoopAction) => boolean;
|
|
500
|
+
baseUrl: string;
|
|
501
|
+
fetch?: typeof fetch;
|
|
502
|
+
jobHref?: string | ((jobId: string) => string);
|
|
503
|
+
jobPollMs?: number;
|
|
504
|
+
jobTimeoutMs?: number;
|
|
505
|
+
logger?: Pick<Console, 'log'>;
|
|
506
|
+
readinessCheckLabel?: string;
|
|
507
|
+
readinessHref?: string;
|
|
508
|
+
recoveryActionsHref?: string;
|
|
509
|
+
refreshHref?: false | string;
|
|
510
|
+
requestTimeoutMs?: number;
|
|
511
|
+
};
|
|
472
512
|
export declare const DEFAULT_VOICE_PROOF_TRENDS_MAX_AGE_MS: number;
|
|
473
513
|
export declare const DEFAULT_VOICE_PROOF_TREND_PROFILE_DEFINITIONS: ({
|
|
474
514
|
description: string;
|
|
@@ -503,6 +543,7 @@ export declare const createVoiceRealCallProfileTraceCollector: <TEvent extends S
|
|
|
503
543
|
export declare const buildVoiceProofTrendProfileSummaries: (input: VoiceProofTrendReport | readonly VoiceProofTrendReport[], options?: VoiceProofTrendProfileSummaryOptions) => VoiceProofTrendProfileSummary[];
|
|
504
544
|
export declare const buildVoiceProofTrendReportFromRealCallProfiles: (options: VoiceProofTrendRealCallProfileReportOptions) => VoiceProofTrendReport;
|
|
505
545
|
export declare const buildVoiceRealCallProfileDefaults: (input: VoiceRealCallProfileHistoryReport | VoiceProofTrendReport, options?: VoiceRealCallProfileDefaultsOptions) => VoiceRealCallProfileDefaultsReport;
|
|
546
|
+
export declare const runVoiceRealCallProfileRecoveryLoop: (options: VoiceRealCallProfileRecoveryLoopOptions) => Promise<VoiceRealCallProfileRecoveryLoopReport>;
|
|
506
547
|
export declare const buildVoiceRealCallProfileRecoveryActions: (report: VoiceRealCallProfileHistoryReport, options?: VoiceRealCallProfileRecoveryActionOptions) => VoiceRealCallProfileRecoveryAction[];
|
|
507
548
|
export declare const createVoiceInMemoryRealCallProfileRecoveryJobStore: (options?: {
|
|
508
549
|
idPrefix?: string;
|
package/dist/react/index.js
CHANGED
|
@@ -2273,6 +2273,136 @@ var appendRealCallRecoveryActionQuery = (href, query) => {
|
|
|
2273
2273
|
const search = new URLSearchParams(entries).toString();
|
|
2274
2274
|
return `${base}${separator}${search}${hash ? `#${hash}` : ""}`;
|
|
2275
2275
|
};
|
|
2276
|
+
var sleepVoiceRealCallProfileRecoveryLoop = (ms) => new Promise((resolve) => setTimeout(resolve, ms));
|
|
2277
|
+
var describeVoiceRealCallProfileRecoveryLoopAction = (action) => [
|
|
2278
|
+
action.label ?? action.id ?? "recovery action",
|
|
2279
|
+
action.profileId ? `profile=${action.profileId}` : undefined,
|
|
2280
|
+
action.href
|
|
2281
|
+
].filter(Boolean).join(" ");
|
|
2282
|
+
var defaultVoiceRealCallProfileRecoveryLoopActionFilter = (action, readinessCheckLabel) => action.method?.toUpperCase() === "POST" && action.sourceCheckLabel === readinessCheckLabel && typeof action.href === "string" && action.href.length > 0;
|
|
2283
|
+
var uniqueVoiceRealCallProfileRecoveryLoopActions = (actions) => {
|
|
2284
|
+
const seen = new Set;
|
|
2285
|
+
return actions.filter((action) => {
|
|
2286
|
+
const key = `${action.method?.toUpperCase() ?? "GET"} ${action.href ?? ""}`;
|
|
2287
|
+
if (seen.has(key)) {
|
|
2288
|
+
return false;
|
|
2289
|
+
}
|
|
2290
|
+
seen.add(key);
|
|
2291
|
+
return true;
|
|
2292
|
+
});
|
|
2293
|
+
};
|
|
2294
|
+
var runVoiceRealCallProfileRecoveryLoop = async (options) => {
|
|
2295
|
+
const baseUrl = options.baseUrl.replace(/\/$/, "");
|
|
2296
|
+
const requestTimeoutMs = options.requestTimeoutMs ?? 5000;
|
|
2297
|
+
const jobPollMs = options.jobPollMs ?? 1200;
|
|
2298
|
+
const jobTimeoutMs = options.jobTimeoutMs ?? 600000;
|
|
2299
|
+
const readinessCheckLabel = options.readinessCheckLabel ?? "Real-call profile history";
|
|
2300
|
+
const fetchImpl = options.fetch ?? fetch;
|
|
2301
|
+
const recoveryActionsHref = options.recoveryActionsHref ?? "/api/production-readiness/recovery-actions";
|
|
2302
|
+
const readinessHref = options.readinessHref ?? "/api/production-readiness";
|
|
2303
|
+
const refreshHref = options.refreshHref === undefined ? "/api/voice/real-call-profile-history/refresh" : options.refreshHref;
|
|
2304
|
+
const jobHref = options.jobHref ?? "/api/voice/real-call-profile-history/actions";
|
|
2305
|
+
const toAbsoluteUrl = (href) => new URL(href, baseUrl).toString();
|
|
2306
|
+
const parseJson = async (response) => {
|
|
2307
|
+
const text = await response.text();
|
|
2308
|
+
try {
|
|
2309
|
+
return JSON.parse(text);
|
|
2310
|
+
} catch (error) {
|
|
2311
|
+
throw new Error(`Expected JSON from ${response.url}, got: ${text.slice(0, 300)}`, { cause: error });
|
|
2312
|
+
}
|
|
2313
|
+
};
|
|
2314
|
+
const fetchJson = async (href, init) => {
|
|
2315
|
+
const response = await fetchImpl(toAbsoluteUrl(href), {
|
|
2316
|
+
headers: { accept: "application/json", ...init?.headers },
|
|
2317
|
+
...init,
|
|
2318
|
+
signal: init?.signal ?? AbortSignal.timeout(requestTimeoutMs)
|
|
2319
|
+
});
|
|
2320
|
+
if (!response.ok) {
|
|
2321
|
+
throw new Error(`${href} returned HTTP ${String(response.status)}.`);
|
|
2322
|
+
}
|
|
2323
|
+
return parseJson(response);
|
|
2324
|
+
};
|
|
2325
|
+
const resolveJobHref = (jobId) => typeof jobHref === "function" ? jobHref(jobId) : `${jobHref.replace(/\/$/, "")}/${jobId}`;
|
|
2326
|
+
const getGate = async (fresh = false) => {
|
|
2327
|
+
const href = fresh ? `${readinessHref}${readinessHref.includes("?") ? "&" : "?"}voiceRecoveryLoopFresh=${String(Date.now())}` : readinessHref;
|
|
2328
|
+
const readiness = await fetchJson(href);
|
|
2329
|
+
return readiness.checks?.find((check) => check.label === readinessCheckLabel) ?? null;
|
|
2330
|
+
};
|
|
2331
|
+
const actionsResponse = await fetchJson(recoveryActionsHref);
|
|
2332
|
+
const actionFilter = options.actionFilter ?? ((action) => defaultVoiceRealCallProfileRecoveryLoopActionFilter(action, readinessCheckLabel));
|
|
2333
|
+
const actions = uniqueVoiceRealCallProfileRecoveryLoopActions((actionsResponse.actions ?? []).filter(actionFilter));
|
|
2334
|
+
if (actions.length === 0) {
|
|
2335
|
+
const realCallProfileGate2 = await getGate();
|
|
2336
|
+
return {
|
|
2337
|
+
actionCount: 0,
|
|
2338
|
+
actions,
|
|
2339
|
+
jobs: [],
|
|
2340
|
+
ok: realCallProfileGate2?.status === "pass",
|
|
2341
|
+
realCallProfileGate: realCallProfileGate2,
|
|
2342
|
+
startFailures: []
|
|
2343
|
+
};
|
|
2344
|
+
}
|
|
2345
|
+
options.logger?.log(`Running ${String(actions.length)} real-call profile recovery action(s) in parallel.`);
|
|
2346
|
+
for (const action of actions) {
|
|
2347
|
+
options.logger?.log(`- ${describeVoiceRealCallProfileRecoveryLoopAction(action)}`);
|
|
2348
|
+
}
|
|
2349
|
+
const starts = await Promise.allSettled(actions.map(async (action) => {
|
|
2350
|
+
if (!action.href) {
|
|
2351
|
+
throw new Error("Recovery action is missing href.");
|
|
2352
|
+
}
|
|
2353
|
+
const body = await fetchJson(action.href, { method: "POST" });
|
|
2354
|
+
return { action, ...body };
|
|
2355
|
+
}));
|
|
2356
|
+
const startedJobs = starts.flatMap((result) => {
|
|
2357
|
+
if (result.status === "rejected") {
|
|
2358
|
+
return [];
|
|
2359
|
+
}
|
|
2360
|
+
return result.value.jobId ? [result.value] : [];
|
|
2361
|
+
});
|
|
2362
|
+
const startFailures = starts.flatMap((result, index) => result.status === "rejected" ? [
|
|
2363
|
+
{
|
|
2364
|
+
action: describeVoiceRealCallProfileRecoveryLoopAction(actions[index] ?? {}),
|
|
2365
|
+
error: result.reason instanceof Error ? result.reason.message : String(result.reason)
|
|
2366
|
+
}
|
|
2367
|
+
] : []);
|
|
2368
|
+
const pollJob = async (jobId) => {
|
|
2369
|
+
const deadline = Date.now() + jobTimeoutMs;
|
|
2370
|
+
while (Date.now() < deadline) {
|
|
2371
|
+
const body = await fetchJson(resolveJobHref(jobId));
|
|
2372
|
+
const job = body.job;
|
|
2373
|
+
if (!job) {
|
|
2374
|
+
throw new Error(`Recovery job ${jobId} was not found.`);
|
|
2375
|
+
}
|
|
2376
|
+
if (job.status === "pass" || job.status === "fail") {
|
|
2377
|
+
return job;
|
|
2378
|
+
}
|
|
2379
|
+
await sleepVoiceRealCallProfileRecoveryLoop(jobPollMs);
|
|
2380
|
+
}
|
|
2381
|
+
throw new Error(`Timed out waiting ${String(jobTimeoutMs)}ms for recovery job ${jobId}.`);
|
|
2382
|
+
};
|
|
2383
|
+
options.logger?.log(`Polling ${String(startedJobs.length)} recovery job(s) in parallel.`);
|
|
2384
|
+
const jobResults = await Promise.allSettled(startedJobs.map((start) => pollJob(start.jobId)));
|
|
2385
|
+
const jobs = jobResults.map((result, index) => ({
|
|
2386
|
+
action: describeVoiceRealCallProfileRecoveryLoopAction(startedJobs[index]?.action ?? {}),
|
|
2387
|
+
jobId: startedJobs[index]?.jobId,
|
|
2388
|
+
result: result.status === "fulfilled" ? result.value : {
|
|
2389
|
+
error: result.reason instanceof Error ? result.reason.message : String(result.reason),
|
|
2390
|
+
status: "fail"
|
|
2391
|
+
}
|
|
2392
|
+
}));
|
|
2393
|
+
if (refreshHref !== false) {
|
|
2394
|
+
await fetchJson(refreshHref, { method: "POST" });
|
|
2395
|
+
}
|
|
2396
|
+
const realCallProfileGate = await getGate(true);
|
|
2397
|
+
return {
|
|
2398
|
+
actionCount: actions.length,
|
|
2399
|
+
actions,
|
|
2400
|
+
jobs,
|
|
2401
|
+
ok: startFailures.length === 0 && jobs.every((job) => job.result.status === "pass") && realCallProfileGate?.status === "pass",
|
|
2402
|
+
realCallProfileGate,
|
|
2403
|
+
startFailures
|
|
2404
|
+
};
|
|
2405
|
+
};
|
|
2276
2406
|
var buildVoiceRealCallProfileRecoveryActions = (report, options = {}) => {
|
|
2277
2407
|
const actions = [
|
|
2278
2408
|
{
|
|
@@ -2608,21 +2738,22 @@ var buildVoiceRealCallProfileHistoryReport = (options = {}) => {
|
|
|
2608
2738
|
];
|
|
2609
2739
|
const passingHistory = history.filter((report) => report.ok === true);
|
|
2610
2740
|
const recommendationHistory = passingHistory.length > 0 ? passingHistory : history;
|
|
2611
|
-
const
|
|
2741
|
+
const profileHistory = history.length > 0 ? history : recommendationHistory;
|
|
2742
|
+
const profiles = buildVoiceProofTrendProfileSummaries(profileHistory, options);
|
|
2612
2743
|
const summary = {
|
|
2613
|
-
cycles:
|
|
2744
|
+
cycles: profileHistory.reduce((total, report) => total + (report.summary.cycles ?? report.cycles.length), 0),
|
|
2614
2745
|
failedReports: history.filter((report) => report.ok !== true).length,
|
|
2615
|
-
maxLiveP95Ms: maxNumber(
|
|
2616
|
-
maxProviderP95Ms: maxNumber(
|
|
2617
|
-
maxTurnP95Ms: maxNumber(
|
|
2746
|
+
maxLiveP95Ms: maxNumber(profileHistory.map(readProofTrendMaxLiveP95)),
|
|
2747
|
+
maxProviderP95Ms: maxNumber(profileHistory.map(readProofTrendMaxProviderP95)),
|
|
2748
|
+
maxTurnP95Ms: maxNumber(profileHistory.map(readProofTrendMaxTurnP95)),
|
|
2618
2749
|
profileCount: profiles.length,
|
|
2619
2750
|
profiles,
|
|
2620
|
-
providers: readProofTrendProviders(
|
|
2621
|
-
runtimeChannel: aggregateProofTrendRuntimeChannel(
|
|
2751
|
+
providers: readProofTrendProviders(profileHistory),
|
|
2752
|
+
runtimeChannel: aggregateProofTrendRuntimeChannel(profileHistory.map(readProofTrendRuntimeChannel).filter((channel) => channel !== undefined))
|
|
2622
2753
|
};
|
|
2623
2754
|
const trend = buildVoiceProofTrendReport({
|
|
2624
2755
|
baseUrl: options.baseUrl,
|
|
2625
|
-
cycles: flattenProofTrendCycles(
|
|
2756
|
+
cycles: flattenProofTrendCycles(profileHistory),
|
|
2626
2757
|
generatedAt,
|
|
2627
2758
|
maxAgeMs: options.maxAgeMs,
|
|
2628
2759
|
now: options.now,
|
package/dist/vue/index.js
CHANGED
|
@@ -2194,6 +2194,136 @@ var appendRealCallRecoveryActionQuery = (href, query) => {
|
|
|
2194
2194
|
const search = new URLSearchParams(entries).toString();
|
|
2195
2195
|
return `${base}${separator}${search}${hash ? `#${hash}` : ""}`;
|
|
2196
2196
|
};
|
|
2197
|
+
var sleepVoiceRealCallProfileRecoveryLoop = (ms) => new Promise((resolve) => setTimeout(resolve, ms));
|
|
2198
|
+
var describeVoiceRealCallProfileRecoveryLoopAction = (action) => [
|
|
2199
|
+
action.label ?? action.id ?? "recovery action",
|
|
2200
|
+
action.profileId ? `profile=${action.profileId}` : undefined,
|
|
2201
|
+
action.href
|
|
2202
|
+
].filter(Boolean).join(" ");
|
|
2203
|
+
var defaultVoiceRealCallProfileRecoveryLoopActionFilter = (action, readinessCheckLabel) => action.method?.toUpperCase() === "POST" && action.sourceCheckLabel === readinessCheckLabel && typeof action.href === "string" && action.href.length > 0;
|
|
2204
|
+
var uniqueVoiceRealCallProfileRecoveryLoopActions = (actions) => {
|
|
2205
|
+
const seen = new Set;
|
|
2206
|
+
return actions.filter((action) => {
|
|
2207
|
+
const key = `${action.method?.toUpperCase() ?? "GET"} ${action.href ?? ""}`;
|
|
2208
|
+
if (seen.has(key)) {
|
|
2209
|
+
return false;
|
|
2210
|
+
}
|
|
2211
|
+
seen.add(key);
|
|
2212
|
+
return true;
|
|
2213
|
+
});
|
|
2214
|
+
};
|
|
2215
|
+
var runVoiceRealCallProfileRecoveryLoop = async (options) => {
|
|
2216
|
+
const baseUrl = options.baseUrl.replace(/\/$/, "");
|
|
2217
|
+
const requestTimeoutMs = options.requestTimeoutMs ?? 5000;
|
|
2218
|
+
const jobPollMs = options.jobPollMs ?? 1200;
|
|
2219
|
+
const jobTimeoutMs = options.jobTimeoutMs ?? 600000;
|
|
2220
|
+
const readinessCheckLabel = options.readinessCheckLabel ?? "Real-call profile history";
|
|
2221
|
+
const fetchImpl = options.fetch ?? fetch;
|
|
2222
|
+
const recoveryActionsHref = options.recoveryActionsHref ?? "/api/production-readiness/recovery-actions";
|
|
2223
|
+
const readinessHref = options.readinessHref ?? "/api/production-readiness";
|
|
2224
|
+
const refreshHref = options.refreshHref === undefined ? "/api/voice/real-call-profile-history/refresh" : options.refreshHref;
|
|
2225
|
+
const jobHref = options.jobHref ?? "/api/voice/real-call-profile-history/actions";
|
|
2226
|
+
const toAbsoluteUrl = (href) => new URL(href, baseUrl).toString();
|
|
2227
|
+
const parseJson = async (response) => {
|
|
2228
|
+
const text = await response.text();
|
|
2229
|
+
try {
|
|
2230
|
+
return JSON.parse(text);
|
|
2231
|
+
} catch (error) {
|
|
2232
|
+
throw new Error(`Expected JSON from ${response.url}, got: ${text.slice(0, 300)}`, { cause: error });
|
|
2233
|
+
}
|
|
2234
|
+
};
|
|
2235
|
+
const fetchJson = async (href, init) => {
|
|
2236
|
+
const response = await fetchImpl(toAbsoluteUrl(href), {
|
|
2237
|
+
headers: { accept: "application/json", ...init?.headers },
|
|
2238
|
+
...init,
|
|
2239
|
+
signal: init?.signal ?? AbortSignal.timeout(requestTimeoutMs)
|
|
2240
|
+
});
|
|
2241
|
+
if (!response.ok) {
|
|
2242
|
+
throw new Error(`${href} returned HTTP ${String(response.status)}.`);
|
|
2243
|
+
}
|
|
2244
|
+
return parseJson(response);
|
|
2245
|
+
};
|
|
2246
|
+
const resolveJobHref = (jobId) => typeof jobHref === "function" ? jobHref(jobId) : `${jobHref.replace(/\/$/, "")}/${jobId}`;
|
|
2247
|
+
const getGate = async (fresh = false) => {
|
|
2248
|
+
const href = fresh ? `${readinessHref}${readinessHref.includes("?") ? "&" : "?"}voiceRecoveryLoopFresh=${String(Date.now())}` : readinessHref;
|
|
2249
|
+
const readiness = await fetchJson(href);
|
|
2250
|
+
return readiness.checks?.find((check) => check.label === readinessCheckLabel) ?? null;
|
|
2251
|
+
};
|
|
2252
|
+
const actionsResponse = await fetchJson(recoveryActionsHref);
|
|
2253
|
+
const actionFilter = options.actionFilter ?? ((action) => defaultVoiceRealCallProfileRecoveryLoopActionFilter(action, readinessCheckLabel));
|
|
2254
|
+
const actions = uniqueVoiceRealCallProfileRecoveryLoopActions((actionsResponse.actions ?? []).filter(actionFilter));
|
|
2255
|
+
if (actions.length === 0) {
|
|
2256
|
+
const realCallProfileGate2 = await getGate();
|
|
2257
|
+
return {
|
|
2258
|
+
actionCount: 0,
|
|
2259
|
+
actions,
|
|
2260
|
+
jobs: [],
|
|
2261
|
+
ok: realCallProfileGate2?.status === "pass",
|
|
2262
|
+
realCallProfileGate: realCallProfileGate2,
|
|
2263
|
+
startFailures: []
|
|
2264
|
+
};
|
|
2265
|
+
}
|
|
2266
|
+
options.logger?.log(`Running ${String(actions.length)} real-call profile recovery action(s) in parallel.`);
|
|
2267
|
+
for (const action of actions) {
|
|
2268
|
+
options.logger?.log(`- ${describeVoiceRealCallProfileRecoveryLoopAction(action)}`);
|
|
2269
|
+
}
|
|
2270
|
+
const starts = await Promise.allSettled(actions.map(async (action) => {
|
|
2271
|
+
if (!action.href) {
|
|
2272
|
+
throw new Error("Recovery action is missing href.");
|
|
2273
|
+
}
|
|
2274
|
+
const body = await fetchJson(action.href, { method: "POST" });
|
|
2275
|
+
return { action, ...body };
|
|
2276
|
+
}));
|
|
2277
|
+
const startedJobs = starts.flatMap((result) => {
|
|
2278
|
+
if (result.status === "rejected") {
|
|
2279
|
+
return [];
|
|
2280
|
+
}
|
|
2281
|
+
return result.value.jobId ? [result.value] : [];
|
|
2282
|
+
});
|
|
2283
|
+
const startFailures = starts.flatMap((result, index) => result.status === "rejected" ? [
|
|
2284
|
+
{
|
|
2285
|
+
action: describeVoiceRealCallProfileRecoveryLoopAction(actions[index] ?? {}),
|
|
2286
|
+
error: result.reason instanceof Error ? result.reason.message : String(result.reason)
|
|
2287
|
+
}
|
|
2288
|
+
] : []);
|
|
2289
|
+
const pollJob = async (jobId) => {
|
|
2290
|
+
const deadline = Date.now() + jobTimeoutMs;
|
|
2291
|
+
while (Date.now() < deadline) {
|
|
2292
|
+
const body = await fetchJson(resolveJobHref(jobId));
|
|
2293
|
+
const job = body.job;
|
|
2294
|
+
if (!job) {
|
|
2295
|
+
throw new Error(`Recovery job ${jobId} was not found.`);
|
|
2296
|
+
}
|
|
2297
|
+
if (job.status === "pass" || job.status === "fail") {
|
|
2298
|
+
return job;
|
|
2299
|
+
}
|
|
2300
|
+
await sleepVoiceRealCallProfileRecoveryLoop(jobPollMs);
|
|
2301
|
+
}
|
|
2302
|
+
throw new Error(`Timed out waiting ${String(jobTimeoutMs)}ms for recovery job ${jobId}.`);
|
|
2303
|
+
};
|
|
2304
|
+
options.logger?.log(`Polling ${String(startedJobs.length)} recovery job(s) in parallel.`);
|
|
2305
|
+
const jobResults = await Promise.allSettled(startedJobs.map((start) => pollJob(start.jobId)));
|
|
2306
|
+
const jobs = jobResults.map((result, index) => ({
|
|
2307
|
+
action: describeVoiceRealCallProfileRecoveryLoopAction(startedJobs[index]?.action ?? {}),
|
|
2308
|
+
jobId: startedJobs[index]?.jobId,
|
|
2309
|
+
result: result.status === "fulfilled" ? result.value : {
|
|
2310
|
+
error: result.reason instanceof Error ? result.reason.message : String(result.reason),
|
|
2311
|
+
status: "fail"
|
|
2312
|
+
}
|
|
2313
|
+
}));
|
|
2314
|
+
if (refreshHref !== false) {
|
|
2315
|
+
await fetchJson(refreshHref, { method: "POST" });
|
|
2316
|
+
}
|
|
2317
|
+
const realCallProfileGate = await getGate(true);
|
|
2318
|
+
return {
|
|
2319
|
+
actionCount: actions.length,
|
|
2320
|
+
actions,
|
|
2321
|
+
jobs,
|
|
2322
|
+
ok: startFailures.length === 0 && jobs.every((job) => job.result.status === "pass") && realCallProfileGate?.status === "pass",
|
|
2323
|
+
realCallProfileGate,
|
|
2324
|
+
startFailures
|
|
2325
|
+
};
|
|
2326
|
+
};
|
|
2197
2327
|
var buildVoiceRealCallProfileRecoveryActions = (report, options = {}) => {
|
|
2198
2328
|
const actions = [
|
|
2199
2329
|
{
|
|
@@ -2529,21 +2659,22 @@ var buildVoiceRealCallProfileHistoryReport = (options = {}) => {
|
|
|
2529
2659
|
];
|
|
2530
2660
|
const passingHistory = history.filter((report) => report.ok === true);
|
|
2531
2661
|
const recommendationHistory = passingHistory.length > 0 ? passingHistory : history;
|
|
2532
|
-
const
|
|
2662
|
+
const profileHistory = history.length > 0 ? history : recommendationHistory;
|
|
2663
|
+
const profiles = buildVoiceProofTrendProfileSummaries(profileHistory, options);
|
|
2533
2664
|
const summary = {
|
|
2534
|
-
cycles:
|
|
2665
|
+
cycles: profileHistory.reduce((total, report) => total + (report.summary.cycles ?? report.cycles.length), 0),
|
|
2535
2666
|
failedReports: history.filter((report) => report.ok !== true).length,
|
|
2536
|
-
maxLiveP95Ms: maxNumber(
|
|
2537
|
-
maxProviderP95Ms: maxNumber(
|
|
2538
|
-
maxTurnP95Ms: maxNumber(
|
|
2667
|
+
maxLiveP95Ms: maxNumber(profileHistory.map(readProofTrendMaxLiveP95)),
|
|
2668
|
+
maxProviderP95Ms: maxNumber(profileHistory.map(readProofTrendMaxProviderP95)),
|
|
2669
|
+
maxTurnP95Ms: maxNumber(profileHistory.map(readProofTrendMaxTurnP95)),
|
|
2539
2670
|
profileCount: profiles.length,
|
|
2540
2671
|
profiles,
|
|
2541
|
-
providers: readProofTrendProviders(
|
|
2542
|
-
runtimeChannel: aggregateProofTrendRuntimeChannel(
|
|
2672
|
+
providers: readProofTrendProviders(profileHistory),
|
|
2673
|
+
runtimeChannel: aggregateProofTrendRuntimeChannel(profileHistory.map(readProofTrendRuntimeChannel).filter((channel) => channel !== undefined))
|
|
2543
2674
|
};
|
|
2544
2675
|
const trend = buildVoiceProofTrendReport({
|
|
2545
2676
|
baseUrl: options.baseUrl,
|
|
2546
|
-
cycles: flattenProofTrendCycles(
|
|
2677
|
+
cycles: flattenProofTrendCycles(profileHistory),
|
|
2547
2678
|
generatedAt,
|
|
2548
2679
|
maxAgeMs: options.maxAgeMs,
|
|
2549
2680
|
now: options.now,
|