@absolutejs/voice 0.0.22-beta.297 → 0.0.22-beta.298
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 +1 -1
- package/dist/index.js +73 -12
- package/dist/providerDecisionTraces.d.ts +9 -1
- package/package.json +1 -1
package/README.md
CHANGED
|
@@ -4116,7 +4116,7 @@ app.use(
|
|
|
4116
4116
|
);
|
|
4117
4117
|
```
|
|
4118
4118
|
|
|
4119
|
-
The routes expose JSON at `/api/voice/provider-decisions`, HTML at `/voice/provider-decisions`, and Markdown at `/voice/provider-decisions.md`. Use this next to provider SLOs when a customer asks not just "is fallback working?" but "why did the system choose this provider for this call?".
|
|
4119
|
+
The routes expose JSON at `/api/voice/provider-decisions`, HTML at `/voice/provider-decisions`, and Markdown at `/voice/provider-decisions.md`. Use this next to provider SLOs when a customer asks not just "is fallback working?" but "why did the system choose this provider for this call?". For proof packs, gate fallback and degradation directly with `minFallbacks`, `minDegraded`, `requiredStatuses`, `requiredFallbackProviders`, and `requiredReasonIncludes` so deploy evidence fails when fallback behavior is missing or unexplained.
|
|
4120
4120
|
|
|
4121
4121
|
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.
|
|
4122
4122
|
|
package/dist/index.js
CHANGED
|
@@ -13146,7 +13146,7 @@ var uniqueSorted = (values) => [
|
|
|
13146
13146
|
].sort();
|
|
13147
13147
|
var createVoiceProviderDecisionTraceEvent = (input) => {
|
|
13148
13148
|
const surface = input.surface ?? surfaceForKind(input.kind);
|
|
13149
|
-
const reason = input.reason ?? (input.status === "fallback" ? `Fallback from ${input.provider} to ${input.fallbackProvider ?? input.selectedProvider ?? "next provider"}.` : input.status === "error" ? `Provider ${input.provider} errored before recovery.` : input.status === "skipped" ? `Provider ${input.provider} was skipped by policy.` : `Provider ${input.selectedProvider ?? input.provider} selected by policy.`);
|
|
13149
|
+
const reason = input.reason ?? (input.status === "degraded" ? `Provider ${input.provider} degraded to ${input.fallbackProvider ?? input.selectedProvider ?? "lower-fidelity fallback"}.` : input.status === "fallback" ? `Fallback from ${input.provider} to ${input.fallbackProvider ?? input.selectedProvider ?? "next provider"}.` : input.status === "error" ? `Provider ${input.provider} errored before recovery.` : input.status === "skipped" ? `Provider ${input.provider} was skipped by policy.` : `Provider ${input.selectedProvider ?? input.provider} selected by policy.`);
|
|
13150
13150
|
return {
|
|
13151
13151
|
at: input.at ?? Date.now(),
|
|
13152
13152
|
payload: {
|
|
@@ -13170,7 +13170,7 @@ var listVoiceProviderDecisionTraces = (events) => {
|
|
|
13170
13170
|
const provider = getString8(event.payload.provider);
|
|
13171
13171
|
const status = getString8(event.payload.status);
|
|
13172
13172
|
const surface = getString8(event.payload.surface);
|
|
13173
|
-
if (!provider || !surface || status !== "error" && status !== "fallback" && status !== "selected" && status !== "skipped" && status !== "success") {
|
|
13173
|
+
if (!provider || !surface || status !== "error" && status !== "fallback" && status !== "degraded" && status !== "selected" && status !== "skipped" && status !== "success") {
|
|
13174
13174
|
return;
|
|
13175
13175
|
}
|
|
13176
13176
|
return {
|
|
@@ -13237,6 +13237,18 @@ var buildVoiceProviderDecisionTraceReport = async (options) => {
|
|
|
13237
13237
|
});
|
|
13238
13238
|
}
|
|
13239
13239
|
}
|
|
13240
|
+
const fallbackCount = decisions.filter((decision) => decision.status === "fallback").length;
|
|
13241
|
+
const degradedCount = decisions.filter((decision) => decision.status === "degraded").length;
|
|
13242
|
+
const statuses = new Set(decisions.map((decision) => decision.status));
|
|
13243
|
+
const providers = uniqueSorted(decisions.flatMap((decision) => [
|
|
13244
|
+
decision.provider,
|
|
13245
|
+
decision.selectedProvider,
|
|
13246
|
+
decision.fallbackProvider
|
|
13247
|
+
]));
|
|
13248
|
+
const fallbackProviders = uniqueSorted(decisions.flatMap((decision) => [
|
|
13249
|
+
decision.fallbackProvider,
|
|
13250
|
+
decision.status === "fallback" || decision.status === "degraded" ? decision.selectedProvider : undefined
|
|
13251
|
+
]));
|
|
13240
13252
|
if (options.minDecisions !== undefined && decisions.length < options.minDecisions) {
|
|
13241
13253
|
issues.push({
|
|
13242
13254
|
code: "voice.provider_decision_trace.min_decisions",
|
|
@@ -13244,9 +13256,60 @@ var buildVoiceProviderDecisionTraceReport = async (options) => {
|
|
|
13244
13256
|
status: "fail"
|
|
13245
13257
|
});
|
|
13246
13258
|
}
|
|
13259
|
+
if (options.minFallbacks !== undefined && fallbackCount < options.minFallbacks) {
|
|
13260
|
+
issues.push({
|
|
13261
|
+
code: "voice.provider_decision_trace.min_fallbacks",
|
|
13262
|
+
message: `Found ${String(fallbackCount)} provider fallback trace(s); expected at least ${String(options.minFallbacks)}.`,
|
|
13263
|
+
status: "fail"
|
|
13264
|
+
});
|
|
13265
|
+
}
|
|
13266
|
+
if (options.minDegraded !== undefined && degradedCount < options.minDegraded) {
|
|
13267
|
+
issues.push({
|
|
13268
|
+
code: "voice.provider_decision_trace.min_degraded",
|
|
13269
|
+
message: `Found ${String(degradedCount)} provider degradation trace(s); expected at least ${String(options.minDegraded)}.`,
|
|
13270
|
+
status: "fail"
|
|
13271
|
+
});
|
|
13272
|
+
}
|
|
13273
|
+
for (const status of options.requiredStatuses ?? []) {
|
|
13274
|
+
if (!statuses.has(status)) {
|
|
13275
|
+
issues.push({
|
|
13276
|
+
code: "voice.provider_decision_trace.status_missing",
|
|
13277
|
+
message: `Missing provider decision status: ${status}.`,
|
|
13278
|
+
status: "fail"
|
|
13279
|
+
});
|
|
13280
|
+
}
|
|
13281
|
+
}
|
|
13282
|
+
for (const provider of options.requiredProviders ?? []) {
|
|
13283
|
+
if (!providers.includes(provider)) {
|
|
13284
|
+
issues.push({
|
|
13285
|
+
code: "voice.provider_decision_trace.provider_missing",
|
|
13286
|
+
message: `Missing provider decision provider: ${provider}.`,
|
|
13287
|
+
status: "fail"
|
|
13288
|
+
});
|
|
13289
|
+
}
|
|
13290
|
+
}
|
|
13291
|
+
for (const provider of options.requiredFallbackProviders ?? []) {
|
|
13292
|
+
if (!fallbackProviders.includes(provider)) {
|
|
13293
|
+
issues.push({
|
|
13294
|
+
code: "voice.provider_decision_trace.fallback_provider_missing",
|
|
13295
|
+
message: `Missing provider decision fallback provider: ${provider}.`,
|
|
13296
|
+
status: "fail"
|
|
13297
|
+
});
|
|
13298
|
+
}
|
|
13299
|
+
}
|
|
13300
|
+
for (const phrase of options.requiredReasonIncludes ?? []) {
|
|
13301
|
+
if (!decisions.some((decision) => decision.reason.includes(phrase))) {
|
|
13302
|
+
issues.push({
|
|
13303
|
+
code: "voice.provider_decision_trace.reason_missing",
|
|
13304
|
+
message: `Missing provider decision reason containing: ${phrase}.`,
|
|
13305
|
+
status: "fail"
|
|
13306
|
+
});
|
|
13307
|
+
}
|
|
13308
|
+
}
|
|
13247
13309
|
const surfaceReports = [...surfaces.entries()].sort(([left], [right]) => left.localeCompare(right)).map(([surface, surfaceDecisions]) => {
|
|
13248
13310
|
const surfaceIssues = issues.filter((issue) => issue.surface === surface);
|
|
13249
13311
|
return {
|
|
13312
|
+
degraded: surfaceDecisions.filter((decision) => decision.status === "degraded").length,
|
|
13250
13313
|
decisions: surfaceDecisions.length,
|
|
13251
13314
|
errors: surfaceDecisions.filter((decision) => decision.status === "error").length,
|
|
13252
13315
|
fallbacks: surfaceDecisions.filter((decision) => decision.status === "fallback").length,
|
|
@@ -13263,20 +13326,16 @@ var buildVoiceProviderDecisionTraceReport = async (options) => {
|
|
|
13263
13326
|
surface
|
|
13264
13327
|
};
|
|
13265
13328
|
});
|
|
13266
|
-
const providers = uniqueSorted(decisions.flatMap((decision) => [
|
|
13267
|
-
decision.provider,
|
|
13268
|
-
decision.selectedProvider,
|
|
13269
|
-
decision.fallbackProvider
|
|
13270
|
-
]));
|
|
13271
13329
|
return {
|
|
13272
13330
|
checkedAt: now,
|
|
13273
13331
|
decisions,
|
|
13274
13332
|
issues,
|
|
13275
13333
|
status: reportStatus(issues),
|
|
13276
13334
|
summary: {
|
|
13335
|
+
degraded: degradedCount,
|
|
13277
13336
|
decisions: decisions.length,
|
|
13278
13337
|
errors: decisions.filter((decision) => decision.status === "error").length,
|
|
13279
|
-
fallbacks:
|
|
13338
|
+
fallbacks: fallbackCount,
|
|
13280
13339
|
providers: providers.length,
|
|
13281
13340
|
selected: decisions.filter((decision) => decision.status === "selected" || decision.status === "success").length,
|
|
13282
13341
|
surfaces: surfaces.size
|
|
@@ -13291,11 +13350,12 @@ var renderVoiceProviderDecisionTraceMarkdown = (report) => [
|
|
|
13291
13350
|
`Decisions: ${String(report.summary.decisions)}`,
|
|
13292
13351
|
`Providers: ${String(report.summary.providers)}`,
|
|
13293
13352
|
`Fallbacks: ${String(report.summary.fallbacks)}`,
|
|
13353
|
+
`Degraded: ${String(report.summary.degraded)}`,
|
|
13294
13354
|
`Errors: ${String(report.summary.errors)}`,
|
|
13295
13355
|
"",
|
|
13296
|
-
"| Surface | Status | Decisions | Selected | Fallbacks | Errors | Providers |",
|
|
13297
|
-
"| --- | --- | ---: | ---: | ---: | ---: | --- |",
|
|
13298
|
-
...report.surfaces.map((surface) => `| ${surface.surface} | ${surface.status} | ${String(surface.decisions)} | ${String(surface.selected)} | ${String(surface.fallbacks)} | ${String(surface.errors)} | ${surface.providers.join(", ")} |`),
|
|
13356
|
+
"| Surface | Status | Decisions | Selected | Fallbacks | Degraded | Errors | Providers |",
|
|
13357
|
+
"| --- | --- | ---: | ---: | ---: | ---: | ---: | --- |",
|
|
13358
|
+
...report.surfaces.map((surface) => `| ${surface.surface} | ${surface.status} | ${String(surface.decisions)} | ${String(surface.selected)} | ${String(surface.fallbacks)} | ${String(surface.degraded)} | ${String(surface.errors)} | ${surface.providers.join(", ")} |`),
|
|
13299
13359
|
"",
|
|
13300
13360
|
...report.issues.map((issue) => `- ${issue.status}: ${issue.message}`)
|
|
13301
13361
|
].join(`
|
|
@@ -13326,12 +13386,13 @@ code{background:#e2e8f0;border-radius:8px;padding:2px 6px}
|
|
|
13326
13386
|
<article class="card"><strong>${String(report.summary.decisions)}</strong><p>decisions</p></article>
|
|
13327
13387
|
<article class="card"><strong>${String(report.summary.providers)}</strong><p>providers</p></article>
|
|
13328
13388
|
<article class="card"><strong>${String(report.summary.fallbacks)}</strong><p>fallbacks</p></article>
|
|
13389
|
+
<article class="card"><strong>${String(report.summary.degraded)}</strong><p>degraded</p></article>
|
|
13329
13390
|
<article class="card"><strong>${String(report.summary.errors)}</strong><p>errors</p></article>
|
|
13330
13391
|
</section>
|
|
13331
13392
|
<section class="surfaces">
|
|
13332
13393
|
${report.surfaces.map((surface) => `<article class="surface">
|
|
13333
13394
|
<header><strong>${escapeHtml17(surface.surface)}</strong> <span class="status ${surface.status}">${escapeHtml17(surface.status)}</span></header>
|
|
13334
|
-
<p>${String(surface.decisions)} decision(s), ${String(surface.fallbacks)} fallback(s), ${String(surface.errors)} error(s).</p>
|
|
13395
|
+
<p>${String(surface.decisions)} decision(s), ${String(surface.fallbacks)} fallback(s), ${String(surface.degraded)} degraded decision(s), ${String(surface.errors)} error(s).</p>
|
|
13335
13396
|
<p class="muted">Providers: ${escapeHtml17(surface.providers.join(", ") || "none")}</p>
|
|
13336
13397
|
<p>${surface.reasons.map((reason) => `<code>${escapeHtml17(reason)}</code>`).join(" ")}</p>
|
|
13337
13398
|
</article>`).join(`
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
import { Elysia } from 'elysia';
|
|
2
2
|
import { type VoiceRoutingEventKind } from './resilienceRoutes';
|
|
3
3
|
import type { StoredVoiceTraceEvent, VoiceTraceEvent, VoiceTraceEventStore } from './trace';
|
|
4
|
-
export type VoiceProviderDecisionStatus = 'error' | 'fallback' | 'selected' | 'skipped' | 'success';
|
|
4
|
+
export type VoiceProviderDecisionStatus = 'degraded' | 'error' | 'fallback' | 'selected' | 'skipped' | 'success';
|
|
5
5
|
export type VoiceProviderDecisionTrace = {
|
|
6
6
|
at: number;
|
|
7
7
|
elapsedMs?: number;
|
|
@@ -31,6 +31,7 @@ export type VoiceProviderDecisionTraceIssue = {
|
|
|
31
31
|
surface?: string;
|
|
32
32
|
};
|
|
33
33
|
export type VoiceProviderDecisionSurfaceReport = {
|
|
34
|
+
degraded: number;
|
|
34
35
|
decisions: number;
|
|
35
36
|
errors: number;
|
|
36
37
|
fallbacks: number;
|
|
@@ -48,6 +49,7 @@ export type VoiceProviderDecisionTraceReport = {
|
|
|
48
49
|
issues: VoiceProviderDecisionTraceIssue[];
|
|
49
50
|
status: 'fail' | 'pass' | 'warn';
|
|
50
51
|
summary: {
|
|
52
|
+
degraded: number;
|
|
51
53
|
decisions: number;
|
|
52
54
|
errors: number;
|
|
53
55
|
fallbacks: number;
|
|
@@ -60,9 +62,15 @@ export type VoiceProviderDecisionTraceReport = {
|
|
|
60
62
|
export type VoiceProviderDecisionTraceReportOptions = {
|
|
61
63
|
events?: StoredVoiceTraceEvent[] | VoiceProviderDecisionTrace[];
|
|
62
64
|
maxAgeMs?: number;
|
|
65
|
+
minDegraded?: number;
|
|
63
66
|
minDecisions?: number;
|
|
67
|
+
minFallbacks?: number;
|
|
64
68
|
now?: number;
|
|
69
|
+
requiredFallbackProviders?: readonly string[];
|
|
70
|
+
requiredProviders?: readonly string[];
|
|
71
|
+
requiredReasonIncludes?: readonly string[];
|
|
65
72
|
requiredSurfaces?: readonly string[];
|
|
73
|
+
requiredStatuses?: readonly VoiceProviderDecisionStatus[];
|
|
66
74
|
sessionId?: string;
|
|
67
75
|
store?: VoiceTraceEventStore;
|
|
68
76
|
};
|