@absolutejs/voice 0.0.22-beta.249 → 0.0.22-beta.250

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/README.md CHANGED
@@ -3938,6 +3938,25 @@ app
3938
3938
 
3939
3939
  The provider SLO routes expose JSON at `/api/voice/provider-slos`, HTML at `/voice/provider-slos`, and Markdown at `/voice/provider-slos.md`. Readiness adds a `Provider SLO gates` check when `providerSlo` is configured; failing latency, timeout, fallback, or unresolved-error budgets close the deploy gate.
3940
3940
 
3941
+ Use `evaluateVoiceProviderSloEvidence(...)` or `assertVoiceProviderSloEvidence(...)` when a proof pack needs to verify the JSON directly instead of scraping rendered HTML. The assertion can require LLM/STT/TTS evidence, latency samples, fallback events, named providers, and per-kind latency ceilings:
3942
+
3943
+ ```ts
3944
+ const providerReport = await buildVoiceProviderSloReport({
3945
+ requiredKinds: ['llm', 'stt', 'tts'],
3946
+ store: runtime.traces
3947
+ });
3948
+
3949
+ assertVoiceProviderSloEvidence(providerReport, {
3950
+ fallbackKinds: ['llm'],
3951
+ maxP95ElapsedMs: { llm: 4500, stt: 1500, tts: 2200 },
3952
+ maxStatus: 'pass',
3953
+ minFallbacks: 1,
3954
+ minLatencySamples: 3,
3955
+ requiredKinds: ['llm', 'stt', 'tts'],
3956
+ requiredProviders: ['openai', 'anthropic', 'deepgram']
3957
+ });
3958
+ ```
3959
+
3941
3960
  Use `createVoiceProviderContractMatrixPreset(...)` when you want readiness proof for the whole provider stack without hand-writing every LLM, STT, and TTS contract row. The preset stays primitive: you still own provider lists, selected providers, latency budgets, env, capabilities, and route mounting.
3942
3961
 
3943
3962
  ```ts
package/dist/index.d.ts CHANGED
@@ -50,7 +50,7 @@ export { createOpenAIVoiceTTS } from './openaiTTS';
50
50
  export { createVoiceProviderHealthHTMLHandler, createVoiceProviderHealthJSONHandler, createVoiceProviderHealthRoutes, renderVoiceProviderHealthHTML, summarizeVoiceProviderHealth } from './providerHealth';
51
51
  export { createVoiceProviderCapabilityHTMLHandler, createVoiceProviderCapabilityJSONHandler, createVoiceProviderCapabilityRoutes, renderVoiceProviderCapabilityHTML, summarizeVoiceProviderCapabilities } from './providerCapabilities';
52
52
  export { assertVoiceProviderRoutingContract, runVoiceProviderRoutingContract } from './providerRoutingContract';
53
- export { buildVoiceProviderSloReport, createVoiceProviderSloRoutes, renderVoiceProviderSloHTML, renderVoiceProviderSloMarkdown } from './providerSlo';
53
+ export { assertVoiceProviderSloEvidence, buildVoiceProviderSloReport, createVoiceProviderSloRoutes, evaluateVoiceProviderSloEvidence, renderVoiceProviderSloHTML, renderVoiceProviderSloMarkdown } from './providerSlo';
54
54
  export { createVoicePhoneAgentProductionSmokeHTMLHandler, createVoicePhoneAgentProductionSmokeJSONHandler, createVoicePhoneAgentProductionSmokeRoutes, renderVoicePhoneAgentProductionSmokeHTML, runVoicePhoneAgentProductionSmokeContract } from './phoneAgentProductionSmoke';
55
55
  export { buildVoiceProductionReadinessGate, buildVoiceProductionReadinessReport, createVoiceProductionReadinessRoutes, renderVoiceProductionReadinessHTML, summarizeVoiceProductionReadinessGate } from './productionReadiness';
56
56
  export { createVoiceReadinessProfile, recommendVoiceReadinessProfile } from './readinessProfiles';
@@ -109,7 +109,7 @@ export type { OpenAIRealtimeAdapterOptions, OpenAIRealtimeModel, OpenAIRealtimeN
109
109
  export type { VoiceProviderHealthStatus, VoiceProviderHealthSummary, VoiceProviderHealthSummaryOptions } from './providerHealth';
110
110
  export type { VoiceProviderCapabilityDefinition, VoiceProviderCapabilityHandlerOptions, VoiceProviderCapabilityHTMLHandlerOptions, VoiceProviderCapabilityKind, VoiceProviderCapabilityOptions, VoiceProviderCapabilityReport, VoiceProviderCapabilityRoutesOptions, VoiceProviderCapabilitySummary } from './providerCapabilities';
111
111
  export type { VoiceProviderRoutingContractDefinition, VoiceProviderRoutingContractIssue, VoiceProviderRoutingContractReport, VoiceProviderRoutingContractRunOptions, VoiceProviderRoutingExpectation, VoiceProviderRoutingStatus } from './providerRoutingContract';
112
- export type { VoiceProviderSloIssue, VoiceProviderSloKindReport, VoiceProviderSloMetric, VoiceProviderSloReport, VoiceProviderSloReportOptions, VoiceProviderSloRoutesOptions, VoiceProviderSloSessionReport, VoiceProviderSloStatus, VoiceProviderSloThresholdConfig, VoiceProviderSloThresholds } from './providerSlo';
112
+ export type { VoiceProviderSloAssertionInput, VoiceProviderSloAssertionReport, VoiceProviderSloIssue, VoiceProviderSloKindReport, VoiceProviderSloMetric, VoiceProviderSloReport, VoiceProviderSloReportOptions, VoiceProviderSloRoutesOptions, VoiceProviderSloSessionReport, VoiceProviderSloStatus, VoiceProviderSloThresholdConfig, VoiceProviderSloThresholds } from './providerSlo';
113
113
  export type { VoiceTurnLatencyHTMLHandlerOptions, VoiceTurnLatencyItem, VoiceTurnLatencyOptions, VoiceTurnLatencyReport, VoiceTurnLatencyRoutesOptions, VoiceTurnLatencyStage, VoiceTurnLatencyStatus } from './turnLatency';
114
114
  export type { VoiceLiveLatencyOptions, VoiceLiveLatencyReport, VoiceLiveLatencyRoutesOptions, VoiceLiveLatencySample, VoiceLiveLatencyStatus } from './liveLatency';
115
115
  export type { VoiceLatencySLOBudget, VoiceLatencySLOGateError, VoiceLatencySLOGateOptions, VoiceLatencySLOGateReport, VoiceLatencySLOMeasurement, VoiceLatencySLOStage, VoiceLatencySLOStageSummary, VoiceLatencySLOStatus } from './latencySlo';
package/dist/index.js CHANGED
@@ -22055,9 +22055,17 @@ var defaultThresholds = {
22055
22055
  }
22056
22056
  };
22057
22057
  var providerKinds = ["llm", "stt", "tts"];
22058
+ var statusRank = {
22059
+ pass: 0,
22060
+ warn: 1,
22061
+ fail: 2
22062
+ };
22058
22063
  var escapeHtml36 = (value) => value.replaceAll("&", "&amp;").replaceAll("<", "&lt;").replaceAll(">", "&gt;").replaceAll('"', "&quot;").replaceAll("'", "&#39;");
22059
22064
  var roundMetric3 = (value) => Math.round(value * 1e4) / 1e4;
22060
22065
  var rate3 = (count, total) => count / Math.max(1, total);
22066
+ var uniqueSorted = (values) => [
22067
+ ...new Set(values.filter((value) => typeof value === "string"))
22068
+ ].sort();
22061
22069
  var percentile3 = (values, rank) => {
22062
22070
  if (values.length === 0) {
22063
22071
  return 0;
@@ -22235,6 +22243,85 @@ var buildVoiceProviderSloReport = async (options = {}) => {
22235
22243
  thresholds
22236
22244
  };
22237
22245
  };
22246
+ var evaluateVoiceProviderSloEvidence = (report, input = {}) => {
22247
+ const issues = [];
22248
+ const kindReports = Object.values(report.kinds);
22249
+ const providers = uniqueSorted(kindReports.flatMap((kind) => kind.providers));
22250
+ const kinds = providerKinds.filter((kind) => report.kinds[kind].events > 0);
22251
+ const fallbacks = kindReports.reduce((total, kind) => total + kind.fallbacks, 0);
22252
+ const timeouts = kindReports.reduce((total, kind) => total + kind.timeouts, 0);
22253
+ const unresolvedErrors = kindReports.reduce((total, kind) => total + kind.unresolvedErrors, 0);
22254
+ const maxStatus = input.maxStatus ?? "pass";
22255
+ if (statusRank[report.status] > statusRank[maxStatus]) {
22256
+ issues.push(`Expected provider SLO status at most ${maxStatus}, found ${report.status}.`);
22257
+ }
22258
+ if (input.minEvents !== undefined && report.events < input.minEvents) {
22259
+ issues.push(`Expected at least ${String(input.minEvents)} provider routing events, found ${String(report.events)}.`);
22260
+ }
22261
+ if (input.minLatencySamples !== undefined && report.eventsWithLatency < input.minLatencySamples) {
22262
+ issues.push(`Expected at least ${String(input.minLatencySamples)} provider latency samples, found ${String(report.eventsWithLatency)}.`);
22263
+ }
22264
+ if (input.minFallbacks !== undefined && fallbacks < input.minFallbacks) {
22265
+ issues.push(`Expected at least ${String(input.minFallbacks)} provider fallback events, found ${String(fallbacks)}.`);
22266
+ }
22267
+ if (input.maxUnresolvedErrors !== undefined && unresolvedErrors > input.maxUnresolvedErrors) {
22268
+ issues.push(`Expected at most ${String(input.maxUnresolvedErrors)} unresolved provider errors, found ${String(unresolvedErrors)}.`);
22269
+ }
22270
+ if (input.maxTimeouts !== undefined && timeouts > input.maxTimeouts) {
22271
+ issues.push(`Expected at most ${String(input.maxTimeouts)} provider timeouts, found ${String(timeouts)}.`);
22272
+ }
22273
+ if (input.maxIssues !== undefined && report.issues.length > input.maxIssues) {
22274
+ issues.push(`Expected at most ${String(input.maxIssues)} provider SLO issues, found ${String(report.issues.length)}.`);
22275
+ }
22276
+ for (const kind of input.requiredKinds ?? []) {
22277
+ if (report.kinds[kind].events === 0) {
22278
+ issues.push(`Missing provider SLO kind evidence: ${kind}.`);
22279
+ }
22280
+ }
22281
+ for (const kind of input.fallbackKinds ?? []) {
22282
+ if (report.kinds[kind].fallbacks === 0) {
22283
+ issues.push(`Missing provider fallback evidence for kind: ${kind}.`);
22284
+ }
22285
+ }
22286
+ for (const provider of input.requiredProviders ?? []) {
22287
+ if (!providers.includes(provider)) {
22288
+ issues.push(`Missing provider SLO provider evidence: ${provider}.`);
22289
+ }
22290
+ }
22291
+ for (const [kind, maxAverageElapsedMs] of Object.entries(input.maxAverageElapsedMs ?? {})) {
22292
+ const metric = report.kinds[kind].metrics.averageElapsedMs;
22293
+ const actual = metric?.actual ?? 0;
22294
+ if (actual > maxAverageElapsedMs) {
22295
+ issues.push(`Expected ${kind} average provider latency <= ${String(maxAverageElapsedMs)}ms, found ${String(actual)}ms.`);
22296
+ }
22297
+ }
22298
+ for (const [kind, maxP95ElapsedMs] of Object.entries(input.maxP95ElapsedMs ?? {})) {
22299
+ const metric = report.kinds[kind].metrics.p95ElapsedMs;
22300
+ const actual = metric?.actual ?? 0;
22301
+ if (actual > maxP95ElapsedMs) {
22302
+ issues.push(`Expected ${kind} p95 provider latency <= ${String(maxP95ElapsedMs)}ms, found ${String(actual)}ms.`);
22303
+ }
22304
+ }
22305
+ return {
22306
+ events: report.events,
22307
+ eventsWithLatency: report.eventsWithLatency,
22308
+ fallbacks,
22309
+ issues,
22310
+ kinds,
22311
+ ok: issues.length === 0,
22312
+ providers,
22313
+ status: report.status,
22314
+ timeouts,
22315
+ unresolvedErrors
22316
+ };
22317
+ };
22318
+ var assertVoiceProviderSloEvidence = (report, input = {}) => {
22319
+ const assertion = evaluateVoiceProviderSloEvidence(report, input);
22320
+ if (!assertion.ok) {
22321
+ throw new Error(`Voice provider SLO assertion failed: ${assertion.issues.join(" ")}`);
22322
+ }
22323
+ return assertion;
22324
+ };
22238
22325
  var formatMetricValue2 = (metric) => metric.unit === "rate" ? `${(metric.actual * 100).toFixed(2)}%` : metric.unit === "ms" ? `${Math.round(metric.actual)}ms` : String(metric.actual);
22239
22326
  var formatMetricThreshold = (metric) => metric.unit === "rate" ? `${(metric.threshold * 100).toFixed(2)}%` : metric.unit === "ms" ? `${Math.round(metric.threshold)}ms` : String(metric.threshold);
22240
22327
  var getMetric = (report, key) => report.metrics[key];
@@ -22880,7 +22967,7 @@ var hasPayloadValue = (payload, key, values) => {
22880
22967
  return typeof value === "string" && values.has(value);
22881
22968
  };
22882
22969
  var countIntegrationDeliveryStatus = (events, status) => events.filter((event) => event.deliveryStatus === status).length;
22883
- var uniqueSorted = (values) => [
22970
+ var uniqueSorted2 = (values) => [
22884
22971
  ...new Set(values.filter((value) => typeof value === "string"))
22885
22972
  ].sort();
22886
22973
  var pushMissingValuesIssue = (input) => {
@@ -23059,11 +23146,11 @@ var buildVoiceOperationsRecord = async (options) => {
23059
23146
  var evaluateVoiceOperationsRecordGuardrails = (record, input = {}) => {
23060
23147
  const issues = [];
23061
23148
  const decisions = record.guardrails.decisions;
23062
- const proofs = uniqueSorted(decisions.map((decision) => decision.proof));
23063
- const ruleIds = uniqueSorted(decisions.flatMap((decision) => decision.findings.map((finding) => finding.ruleId)));
23064
- const stages = uniqueSorted(decisions.map((decision) => decision.stage));
23065
- const statuses = uniqueSorted(decisions.map((decision) => decision.status));
23066
- const toolNames = uniqueSorted(decisions.map((decision) => decision.toolName));
23149
+ const proofs = uniqueSorted2(decisions.map((decision) => decision.proof));
23150
+ const ruleIds = uniqueSorted2(decisions.flatMap((decision) => decision.findings.map((finding) => finding.ruleId)));
23151
+ const stages = uniqueSorted2(decisions.map((decision) => decision.stage));
23152
+ const statuses = uniqueSorted2(decisions.map((decision) => decision.status));
23153
+ const toolNames = uniqueSorted2(decisions.map((decision) => decision.toolName));
23067
23154
  const minDecisions = input.minDecisions ?? 1;
23068
23155
  if (record.guardrails.total < minDecisions) {
23069
23156
  issues.push(`Expected at least ${String(minDecisions)} guardrail decisions, found ${String(record.guardrails.total)}.`);
@@ -29986,6 +30073,7 @@ export {
29986
30073
  evaluateVoiceTelephonyContract,
29987
30074
  evaluateVoiceQuality,
29988
30075
  evaluateVoiceProviderStackGaps,
30076
+ evaluateVoiceProviderSloEvidence,
29989
30077
  evaluateVoiceOperationsRecordGuardrails,
29990
30078
  evaluateVoiceGuardrailPolicy,
29991
30079
  encodeTwilioMulawBase64,
@@ -30299,6 +30387,7 @@ export {
30299
30387
  buildVoiceAuditDeliveryReport,
30300
30388
  buildEmptyVoiceProofTrendReport,
30301
30389
  assignVoiceOpsTask,
30390
+ assertVoiceProviderSloEvidence,
30302
30391
  assertVoiceProviderRoutingContract,
30303
30392
  assertVoiceOperationsRecordGuardrails,
30304
30393
  assertVoiceObservabilityExportSchema,
@@ -79,7 +79,35 @@ export type VoiceProviderSloRoutesOptions = VoiceProviderSloReportOptions & {
79
79
  render?: (report: VoiceProviderSloReport) => string | Promise<string>;
80
80
  title?: string;
81
81
  };
82
+ export type VoiceProviderSloAssertionInput = {
83
+ fallbackKinds?: VoiceRoutingEventKind[];
84
+ maxAverageElapsedMs?: Partial<Record<VoiceRoutingEventKind, number>>;
85
+ maxIssues?: number;
86
+ maxP95ElapsedMs?: Partial<Record<VoiceRoutingEventKind, number>>;
87
+ maxStatus?: VoiceProviderSloStatus;
88
+ maxTimeouts?: number;
89
+ maxUnresolvedErrors?: number;
90
+ minEvents?: number;
91
+ minFallbacks?: number;
92
+ minLatencySamples?: number;
93
+ requiredKinds?: VoiceRoutingEventKind[];
94
+ requiredProviders?: string[];
95
+ };
96
+ export type VoiceProviderSloAssertionReport = {
97
+ events: number;
98
+ eventsWithLatency: number;
99
+ fallbacks: number;
100
+ issues: string[];
101
+ kinds: VoiceRoutingEventKind[];
102
+ ok: boolean;
103
+ providers: string[];
104
+ status: VoiceProviderSloStatus;
105
+ timeouts: number;
106
+ unresolvedErrors: number;
107
+ };
82
108
  export declare const buildVoiceProviderSloReport: (options?: VoiceProviderSloReportOptions) => Promise<VoiceProviderSloReport>;
109
+ export declare const evaluateVoiceProviderSloEvidence: (report: VoiceProviderSloReport, input?: VoiceProviderSloAssertionInput) => VoiceProviderSloAssertionReport;
110
+ export declare const assertVoiceProviderSloEvidence: (report: VoiceProviderSloReport, input?: VoiceProviderSloAssertionInput) => VoiceProviderSloAssertionReport;
83
111
  export declare const renderVoiceProviderSloMarkdown: (report: VoiceProviderSloReport) => string;
84
112
  export declare const renderVoiceProviderSloHTML: (report: VoiceProviderSloReport, options?: {
85
113
  title?: string;
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@absolutejs/voice",
3
- "version": "0.0.22-beta.249",
3
+ "version": "0.0.22-beta.250",
4
4
  "description": "Voice primitives and Elysia plugin for AbsoluteJS",
5
5
  "repository": {
6
6
  "type": "git",