@absolutejs/voice 0.0.22-beta.182 → 0.0.22-beta.184
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/index.d.ts +3 -1
- package/dist/index.js +603 -400
- package/dist/operationsRecord.d.ts +102 -0
- package/dist/productionReadiness.d.ts +14 -0
- package/package.json +1 -1
package/dist/index.js
CHANGED
|
@@ -20482,6 +20482,7 @@ var readinessGateCodes = {
|
|
|
20482
20482
|
"Delivery runtime": "voice.readiness.delivery_runtime",
|
|
20483
20483
|
"Handoff delivery": "voice.readiness.handoff_delivery",
|
|
20484
20484
|
"Live latency proof": "voice.readiness.live_latency",
|
|
20485
|
+
"Operations records": "voice.readiness.operations_records",
|
|
20485
20486
|
"Operator action history": "voice.readiness.operator_action_history",
|
|
20486
20487
|
"Phone agent production smoke": "voice.readiness.phone_agent_smoke",
|
|
20487
20488
|
"Provider contract matrix": "voice.readiness.provider_contract_matrix",
|
|
@@ -20810,6 +20811,46 @@ var summarizeLiveLatency = (events, options) => {
|
|
|
20810
20811
|
warnings
|
|
20811
20812
|
};
|
|
20812
20813
|
};
|
|
20814
|
+
var getString12 = (value) => typeof value === "string" ? value : undefined;
|
|
20815
|
+
var getNumber7 = (value) => typeof value === "number" && Number.isFinite(value) ? value : undefined;
|
|
20816
|
+
var voiceOperationsRecordHref = (base, sessionId) => {
|
|
20817
|
+
const encoded = encodeURIComponent(sessionId);
|
|
20818
|
+
if (base.includes(":sessionId")) {
|
|
20819
|
+
return base.replace(":sessionId", encoded);
|
|
20820
|
+
}
|
|
20821
|
+
return `${base.replace(/\/+$/, "")}/${encoded}`;
|
|
20822
|
+
};
|
|
20823
|
+
var buildOperationsRecordLinks = (input) => {
|
|
20824
|
+
const failedSessionSet = new Set(input.failedSessionIds);
|
|
20825
|
+
const providerErrors = input.events.filter((event) => event.type === "session.error" && (event.payload.providerStatus === "error" || typeof event.payload.error === "string")).map((event) => ({
|
|
20826
|
+
detail: getString12(event.payload.error),
|
|
20827
|
+
href: voiceOperationsRecordHref(input.base, event.sessionId),
|
|
20828
|
+
label: "Open provider error operations record",
|
|
20829
|
+
sessionId: event.sessionId,
|
|
20830
|
+
status: "fail"
|
|
20831
|
+
}));
|
|
20832
|
+
const failingLatency = input.events.filter((event) => event.type === "client.live_latency").map((event) => ({
|
|
20833
|
+
event,
|
|
20834
|
+
latencyMs: getNumber7(event.payload.latencyMs) ?? getNumber7(event.payload.elapsedMs)
|
|
20835
|
+
})).filter((entry) => entry.latencyMs !== undefined && entry.latencyMs > input.liveLatencyWarnAfterMs).map(({ event, latencyMs }) => ({
|
|
20836
|
+
detail: `${latencyMs}ms live latency`,
|
|
20837
|
+
href: voiceOperationsRecordHref(input.base, event.sessionId),
|
|
20838
|
+
label: "Open latency operations record",
|
|
20839
|
+
sessionId: event.sessionId,
|
|
20840
|
+
status: latencyMs > input.liveLatencyFailAfterMs ? "fail" : "warn"
|
|
20841
|
+
}));
|
|
20842
|
+
return {
|
|
20843
|
+
failedSessions: input.failedSessionIds.map((sessionId) => ({
|
|
20844
|
+
href: voiceOperationsRecordHref(input.base, sessionId),
|
|
20845
|
+
label: "Open failed session operations record",
|
|
20846
|
+
sessionId,
|
|
20847
|
+
status: failedSessionSet.has(sessionId) ? "fail" : "warn"
|
|
20848
|
+
})),
|
|
20849
|
+
failingLatency,
|
|
20850
|
+
providerErrors
|
|
20851
|
+
};
|
|
20852
|
+
};
|
|
20853
|
+
var firstOperationsRecordHref = (links) => links[0]?.href;
|
|
20813
20854
|
var buildVoiceProductionReadinessReport = async (options, input = {}) => {
|
|
20814
20855
|
const request = input.request ?? new Request("http://localhost/");
|
|
20815
20856
|
const query = input.query ?? {};
|
|
@@ -20873,6 +20914,14 @@ var buildVoiceProductionReadinessReport = async (options, input = {}) => {
|
|
|
20873
20914
|
const deliveryRuntime = summarizeDeliveryRuntime(deliveryRuntimeSummary);
|
|
20874
20915
|
const degradedProviders = providers.filter((provider) => provider.status === "degraded" || provider.status === "rate-limited" || provider.status === "suppressed").length;
|
|
20875
20916
|
const failedSessions = sessions.filter((session) => session.status === "failed").length;
|
|
20917
|
+
const failedSessionItems = sessions.filter((session) => session.status === "failed");
|
|
20918
|
+
const operationsRecords = buildOperationsRecordLinks({
|
|
20919
|
+
base: options.links?.operationsRecords ?? "/voice-operations",
|
|
20920
|
+
events,
|
|
20921
|
+
failedSessionIds: failedSessionItems.map((session) => session.sessionId),
|
|
20922
|
+
liveLatencyFailAfterMs: options.liveLatencyFailAfterMs ?? 3200,
|
|
20923
|
+
liveLatencyWarnAfterMs: options.liveLatencyWarnAfterMs ?? 1800
|
|
20924
|
+
});
|
|
20876
20925
|
const checks = [
|
|
20877
20926
|
{
|
|
20878
20927
|
detail: quality.status === "pass" ? "Quality gates are passing." : "Quality gates need attention.",
|
|
@@ -20904,11 +20953,18 @@ var buildVoiceProductionReadinessReport = async (options, input = {}) => {
|
|
|
20904
20953
|
},
|
|
20905
20954
|
{
|
|
20906
20955
|
detail: providerRecovery.unresolvedErrors > 0 ? `${providerRecovery.unresolvedErrors} provider error(s) have no recovered fallback evidence.` : providerRecovery.recovered > 0 ? `${providerRecovery.recovered} provider fallback recovery event(s) kept sessions healthy.` : "No provider fallback recovery was needed in the current trace window.",
|
|
20907
|
-
href: options.links?.resilience ?? "/resilience",
|
|
20956
|
+
href: firstOperationsRecordHref(operationsRecords.providerErrors) ?? options.links?.resilience ?? "/resilience",
|
|
20908
20957
|
label: "Provider fallback recovery",
|
|
20909
20958
|
status: providerRecovery.status,
|
|
20910
20959
|
value: providerRecovery.total === 0 ? "0 events" : `${providerRecovery.recovered}/${providerRecovery.total}`,
|
|
20911
20960
|
actions: providerRecovery.status === "pass" ? [] : [
|
|
20961
|
+
...firstOperationsRecordHref(operationsRecords.providerErrors) ? [
|
|
20962
|
+
{
|
|
20963
|
+
description: "Open the exact call/session operations record for the first unresolved provider error.",
|
|
20964
|
+
href: firstOperationsRecordHref(operationsRecords.providerErrors),
|
|
20965
|
+
label: "Open failing operations record"
|
|
20966
|
+
}
|
|
20967
|
+
] : [],
|
|
20912
20968
|
{
|
|
20913
20969
|
description: "Open provider resilience traces and inspect unresolved provider errors.",
|
|
20914
20970
|
href: options.links?.resilience ?? "/resilience",
|
|
@@ -20918,11 +20974,18 @@ var buildVoiceProductionReadinessReport = async (options, input = {}) => {
|
|
|
20918
20974
|
},
|
|
20919
20975
|
{
|
|
20920
20976
|
detail: failedSessions === 0 ? sessions.length > 0 ? "Recent sessions have no recorded provider/session failures." : "No sessions have been recorded yet; run a smoke or live session for proof." : `${failedSessions} recent session(s) have failures.`,
|
|
20921
|
-
href: options.links?.sessions ?? "/sessions",
|
|
20977
|
+
href: firstOperationsRecordHref(operationsRecords.failedSessions) ?? options.links?.sessions ?? "/sessions",
|
|
20922
20978
|
label: "Session health",
|
|
20923
20979
|
status: failedSessions > 0 ? "fail" : sessions.length === 0 ? "warn" : "pass",
|
|
20924
20980
|
value: `${sessions.length - failedSessions}/${sessions.length}`,
|
|
20925
20981
|
actions: failedSessions > 0 ? [
|
|
20982
|
+
...firstOperationsRecordHref(operationsRecords.failedSessions) ? [
|
|
20983
|
+
{
|
|
20984
|
+
description: "Open the exact failed call/session operations record.",
|
|
20985
|
+
href: firstOperationsRecordHref(operationsRecords.failedSessions),
|
|
20986
|
+
label: "Open failed operations record"
|
|
20987
|
+
}
|
|
20988
|
+
] : [],
|
|
20926
20989
|
{
|
|
20927
20990
|
description: "Open failed sessions and replay traces.",
|
|
20928
20991
|
href: `${options.links?.sessions ?? "/sessions"}?status=failed`,
|
|
@@ -20974,12 +21037,19 @@ var buildVoiceProductionReadinessReport = async (options, input = {}) => {
|
|
|
20974
21037
|
const proofSource = (...keys) => keys.map((key) => proofSources?.[key]).find((source) => source !== undefined);
|
|
20975
21038
|
checks.push({
|
|
20976
21039
|
detail: liveLatency.total === 0 ? "No browser live-latency measurements are recorded yet." : liveLatency.status === "pass" ? `Live browser turn latency averages ${liveLatency.averageLatencyMs}ms.` : `${liveLatency.failed} failed and ${liveLatency.warnings} warned live-latency measurement(s).`,
|
|
20977
|
-
href: options.links?.liveLatency ?? "/traces",
|
|
21040
|
+
href: firstOperationsRecordHref(operationsRecords.failingLatency) ?? options.links?.liveLatency ?? "/traces",
|
|
20978
21041
|
label: "Live latency proof",
|
|
20979
21042
|
proofSource: proofSource("liveLatency", "liveLatencyProof"),
|
|
20980
21043
|
status: liveLatency.status,
|
|
20981
21044
|
value: liveLatency.averageLatencyMs === undefined ? `${liveLatency.total} samples` : `${liveLatency.averageLatencyMs}ms avg`,
|
|
20982
21045
|
actions: liveLatency.status === "pass" ? [] : [
|
|
21046
|
+
...firstOperationsRecordHref(operationsRecords.failingLatency) ? [
|
|
21047
|
+
{
|
|
21048
|
+
description: "Open the exact call/session operations record for the slow live turn.",
|
|
21049
|
+
href: firstOperationsRecordHref(operationsRecords.failingLatency),
|
|
21050
|
+
label: "Open latency operations record"
|
|
21051
|
+
}
|
|
21052
|
+
] : [],
|
|
20983
21053
|
{
|
|
20984
21054
|
description: "Run a live browser voice turn and inspect the persisted latency trace.",
|
|
20985
21055
|
href: options.links?.liveLatency ?? "/traces",
|
|
@@ -21260,6 +21330,7 @@ var buildVoiceProductionReadinessReport = async (options, input = {}) => {
|
|
|
21260
21330
|
handoffs: "/handoffs",
|
|
21261
21331
|
handoffRetry: "/api/voice-handoffs/retry",
|
|
21262
21332
|
liveLatency: "/traces",
|
|
21333
|
+
operationsRecords: "/voice-operations",
|
|
21263
21334
|
opsActions: "/voice/ops-actions",
|
|
21264
21335
|
phoneAgentSmoke: "/sessions",
|
|
21265
21336
|
providerContracts: "/provider-contracts",
|
|
@@ -21272,6 +21343,7 @@ var buildVoiceProductionReadinessReport = async (options, input = {}) => {
|
|
|
21272
21343
|
...options.links
|
|
21273
21344
|
},
|
|
21274
21345
|
profile: options.profile || undefined,
|
|
21346
|
+
operationsRecords,
|
|
21275
21347
|
proofSources,
|
|
21276
21348
|
status: rollupStatus2(checks),
|
|
21277
21349
|
summary: {
|
|
@@ -22187,131 +22259,516 @@ var createVoiceOpsConsoleRoutes = (options) => {
|
|
|
22187
22259
|
routes.get(`${path}/json`, async () => getReport());
|
|
22188
22260
|
return routes;
|
|
22189
22261
|
};
|
|
22190
|
-
// src/
|
|
22191
|
-
|
|
22192
|
-
|
|
22193
|
-
|
|
22194
|
-
|
|
22195
|
-
|
|
22196
|
-
|
|
22197
|
-
|
|
22198
|
-
|
|
22199
|
-
|
|
22200
|
-
|
|
22201
|
-
|
|
22202
|
-
|
|
22203
|
-
|
|
22204
|
-
{
|
|
22205
|
-
description: "Provider routing, fallback, and resilience controls.",
|
|
22206
|
-
href: "/resilience",
|
|
22207
|
-
label: "Resilience"
|
|
22208
|
-
},
|
|
22209
|
-
{
|
|
22210
|
-
description: "One JSON/HTML production readiness rollup.",
|
|
22211
|
-
href: "/production-readiness",
|
|
22212
|
-
label: "Production Readiness",
|
|
22213
|
-
statusHref: "/api/production-readiness"
|
|
22214
|
-
},
|
|
22215
|
-
{
|
|
22216
|
-
description: "Recent sessions and replay links.",
|
|
22217
|
-
href: "/sessions",
|
|
22218
|
-
label: "Sessions"
|
|
22219
|
-
},
|
|
22220
|
-
{
|
|
22221
|
-
description: "Trace-backed phone-agent production smoke proof.",
|
|
22222
|
-
href: "/voice/phone/smoke-contract",
|
|
22223
|
-
label: "Phone Smoke",
|
|
22224
|
-
statusHref: "/api/voice/phone/smoke-contract"
|
|
22262
|
+
// src/operationsRecord.ts
|
|
22263
|
+
import { Elysia as Elysia37 } from "elysia";
|
|
22264
|
+
|
|
22265
|
+
// src/traceTimeline.ts
|
|
22266
|
+
import { Elysia as Elysia36 } from "elysia";
|
|
22267
|
+
var escapeHtml38 = (value) => value.replaceAll("&", "&").replaceAll("<", "<").replaceAll(">", ">").replaceAll('"', """).replaceAll("'", "'");
|
|
22268
|
+
var getString13 = (value) => typeof value === "string" && value.trim() ? value : undefined;
|
|
22269
|
+
var getNumber8 = (value) => typeof value === "number" && Number.isFinite(value) ? value : undefined;
|
|
22270
|
+
var firstString3 = (payload, keys) => {
|
|
22271
|
+
for (const key of keys) {
|
|
22272
|
+
const value = getString13(payload[key]);
|
|
22273
|
+
if (value) {
|
|
22274
|
+
return value;
|
|
22275
|
+
}
|
|
22225
22276
|
}
|
|
22226
|
-
|
|
22227
|
-
|
|
22228
|
-
|
|
22229
|
-
|
|
22230
|
-
|
|
22231
|
-
|
|
22232
|
-
|
|
22233
|
-
|
|
22234
|
-
const shouldInclude = (surface) => include?.[surface] !== false;
|
|
22235
|
-
const evals = options.evals === false ? undefined : options.evals;
|
|
22236
|
-
const events = filterVoiceTraceEvents(await options.store.list());
|
|
22237
|
-
const [quality, workflows, providers, sessions, handoffs, deliverySinks] = await Promise.all([
|
|
22238
|
-
options.quality === false || !shouldInclude("quality") ? undefined : evaluateVoiceQuality({
|
|
22239
|
-
events,
|
|
22240
|
-
thresholds: options.quality?.thresholds
|
|
22241
|
-
}),
|
|
22242
|
-
!evals || !shouldInclude("workflows") ? undefined : (async () => {
|
|
22243
|
-
const fixtureReport = await runVoiceScenarioFixtureEvals({
|
|
22244
|
-
fixtures: evals.fixtures,
|
|
22245
|
-
fixtureStore: evals.fixtureStore,
|
|
22246
|
-
scenarios: evals.scenarios
|
|
22247
|
-
});
|
|
22248
|
-
if ((options.preferFixtureWorkflows ?? true) && fixtureReport.total > 0) {
|
|
22249
|
-
return {
|
|
22250
|
-
failed: fixtureReport.failed,
|
|
22251
|
-
source: "fixtures",
|
|
22252
|
-
status: fixtureReport.status,
|
|
22253
|
-
total: fixtureReport.total
|
|
22254
|
-
};
|
|
22255
|
-
}
|
|
22256
|
-
const liveReport = await runVoiceScenarioEvals({
|
|
22257
|
-
events,
|
|
22258
|
-
scenarios: evals.scenarios
|
|
22259
|
-
});
|
|
22260
|
-
return {
|
|
22261
|
-
failed: liveReport.failed,
|
|
22262
|
-
source: "live",
|
|
22263
|
-
status: liveReport.status,
|
|
22264
|
-
total: liveReport.total
|
|
22265
|
-
};
|
|
22266
|
-
})(),
|
|
22267
|
-
!shouldInclude("providers") ? undefined : Promise.all([
|
|
22268
|
-
summarizeVoiceProviderHealth({
|
|
22269
|
-
events,
|
|
22270
|
-
providers: options.llmProviders
|
|
22271
|
-
}),
|
|
22272
|
-
summarizeVoiceProviderHealth({
|
|
22273
|
-
events: events.filter((event) => event.payload.kind === "stt"),
|
|
22274
|
-
providers: options.sttProviders
|
|
22275
|
-
}),
|
|
22276
|
-
summarizeVoiceProviderHealth({
|
|
22277
|
-
events: events.filter((event) => event.payload.kind === "tts"),
|
|
22278
|
-
providers: options.ttsProviders
|
|
22279
|
-
})
|
|
22280
|
-
]).then((groups) => groups.flat()),
|
|
22281
|
-
!shouldInclude("sessions") ? undefined : summarizeVoiceSessions({
|
|
22282
|
-
events
|
|
22283
|
-
}),
|
|
22284
|
-
!shouldInclude("handoffs") ? undefined : summarizeVoiceHandoffHealth({
|
|
22285
|
-
events
|
|
22286
|
-
}),
|
|
22287
|
-
!options.deliverySinks || !shouldInclude("deliverySinks") ? undefined : buildVoiceDeliverySinkReport(options.deliverySinks)
|
|
22288
|
-
]);
|
|
22289
|
-
const providerRecovery = shouldInclude("providerRecovery") ? summarizeVoiceProviderFallbackRecovery(events) : undefined;
|
|
22290
|
-
const surfaces = {};
|
|
22291
|
-
const statuses = [];
|
|
22292
|
-
if (quality) {
|
|
22293
|
-
surfaces.quality = { status: quality.status };
|
|
22294
|
-
statuses.push(quality.status);
|
|
22277
|
+
return;
|
|
22278
|
+
};
|
|
22279
|
+
var firstNumber3 = (payload, keys) => {
|
|
22280
|
+
for (const key of keys) {
|
|
22281
|
+
const value = getNumber8(payload[key]);
|
|
22282
|
+
if (value !== undefined) {
|
|
22283
|
+
return value;
|
|
22284
|
+
}
|
|
22295
22285
|
}
|
|
22296
|
-
|
|
22297
|
-
|
|
22298
|
-
|
|
22286
|
+
return;
|
|
22287
|
+
};
|
|
22288
|
+
var eventProvider = (event) => firstString3(event.payload, [
|
|
22289
|
+
"provider",
|
|
22290
|
+
"selectedProvider",
|
|
22291
|
+
"fallbackProvider",
|
|
22292
|
+
"variantId"
|
|
22293
|
+
]);
|
|
22294
|
+
var eventStatus = (event) => firstString3(event.payload, [
|
|
22295
|
+
"providerStatus",
|
|
22296
|
+
"status",
|
|
22297
|
+
"disposition",
|
|
22298
|
+
"type",
|
|
22299
|
+
"reason"
|
|
22300
|
+
]);
|
|
22301
|
+
var eventElapsedMs = (event) => firstNumber3(event.payload, ["elapsedMs", "latencyMs", "durationMs"]);
|
|
22302
|
+
var timelineLabel = (event) => {
|
|
22303
|
+
switch (event.type) {
|
|
22304
|
+
case "call.lifecycle":
|
|
22305
|
+
return `Call ${eventStatus(event) ?? "lifecycle"}`;
|
|
22306
|
+
case "turn.transcript":
|
|
22307
|
+
return event.payload.isFinal === true ? "Final transcript" : "Partial transcript";
|
|
22308
|
+
case "turn.committed":
|
|
22309
|
+
return `Committed turn${getString13(event.payload.reason) ? ` (${getString13(event.payload.reason)})` : ""}`;
|
|
22310
|
+
case "turn.assistant":
|
|
22311
|
+
return "Assistant reply";
|
|
22312
|
+
case "agent.model":
|
|
22313
|
+
return `Model call${eventProvider(event) ? ` via ${eventProvider(event)}` : ""}`;
|
|
22314
|
+
case "agent.tool":
|
|
22315
|
+
return `Tool ${getString13(event.payload.toolName) ?? "call"}`;
|
|
22316
|
+
case "agent.handoff":
|
|
22317
|
+
return `Agent handoff${getString13(event.payload.targetAgentId) ? ` to ${getString13(event.payload.targetAgentId)}` : ""}`;
|
|
22318
|
+
case "assistant.run":
|
|
22319
|
+
return `Assistant run${eventProvider(event) ? ` via ${eventProvider(event)}` : ""}`;
|
|
22320
|
+
case "assistant.guardrail":
|
|
22321
|
+
return `Guardrail ${eventStatus(event) ?? "check"}`;
|
|
22322
|
+
case "call.handoff":
|
|
22323
|
+
return `Call handoff ${eventStatus(event) ?? ""}`.trim();
|
|
22324
|
+
case "client.live_latency":
|
|
22325
|
+
return `Live latency${eventElapsedMs(event) !== undefined ? ` ${eventElapsedMs(event)}ms` : ""}`;
|
|
22326
|
+
case "session.error":
|
|
22327
|
+
return `Error${getString13(event.payload.error) ? `: ${getString13(event.payload.error)}` : ""}`;
|
|
22328
|
+
case "turn.cost":
|
|
22329
|
+
return "Cost telemetry";
|
|
22330
|
+
case "turn_latency.stage":
|
|
22331
|
+
return `Latency ${getString13(event.payload.stage) ?? "stage"}`;
|
|
22332
|
+
case "workflow.contract":
|
|
22333
|
+
return `Workflow contract ${eventStatus(event) ?? ""}`.trim();
|
|
22334
|
+
default:
|
|
22335
|
+
return event.type;
|
|
22299
22336
|
}
|
|
22300
|
-
|
|
22301
|
-
|
|
22302
|
-
|
|
22303
|
-
|
|
22304
|
-
|
|
22305
|
-
|
|
22306
|
-
|
|
22337
|
+
};
|
|
22338
|
+
var summarizeProviders = (events) => {
|
|
22339
|
+
const entries = new Map;
|
|
22340
|
+
const getEntry = (provider) => {
|
|
22341
|
+
const existing = entries.get(provider);
|
|
22342
|
+
if (existing) {
|
|
22343
|
+
return existing;
|
|
22344
|
+
}
|
|
22345
|
+
const entry = {
|
|
22346
|
+
elapsed: [],
|
|
22347
|
+
errorCount: 0,
|
|
22348
|
+
eventCount: 0,
|
|
22349
|
+
fallbackCount: 0,
|
|
22350
|
+
successCount: 0,
|
|
22351
|
+
timeoutCount: 0
|
|
22307
22352
|
};
|
|
22308
|
-
|
|
22309
|
-
|
|
22310
|
-
|
|
22311
|
-
|
|
22312
|
-
|
|
22353
|
+
entries.set(provider, entry);
|
|
22354
|
+
return entry;
|
|
22355
|
+
};
|
|
22356
|
+
for (const event of events) {
|
|
22357
|
+
const provider = eventProvider(event);
|
|
22358
|
+
if (!provider) {
|
|
22359
|
+
continue;
|
|
22360
|
+
}
|
|
22361
|
+
const entry = getEntry(provider);
|
|
22362
|
+
const status = eventStatus(event);
|
|
22363
|
+
const elapsedMs = eventElapsedMs(event);
|
|
22364
|
+
entry.eventCount += 1;
|
|
22365
|
+
if (elapsedMs !== undefined) {
|
|
22366
|
+
entry.elapsed.push(elapsedMs);
|
|
22367
|
+
}
|
|
22368
|
+
if (status === "success") {
|
|
22369
|
+
entry.successCount += 1;
|
|
22370
|
+
}
|
|
22371
|
+
if (status === "fallback") {
|
|
22372
|
+
entry.fallbackCount += 1;
|
|
22373
|
+
}
|
|
22374
|
+
if (status === "error") {
|
|
22375
|
+
entry.errorCount += 1;
|
|
22376
|
+
}
|
|
22377
|
+
if (status === "timeout") {
|
|
22378
|
+
entry.timeoutCount += 1;
|
|
22379
|
+
}
|
|
22313
22380
|
}
|
|
22314
|
-
|
|
22381
|
+
return [...entries.entries()].map(([provider, entry]) => ({
|
|
22382
|
+
averageElapsedMs: entry.elapsed.length > 0 ? Math.round(entry.elapsed.reduce((total, value) => total + value, 0) / entry.elapsed.length) : undefined,
|
|
22383
|
+
errorCount: entry.errorCount,
|
|
22384
|
+
eventCount: entry.eventCount,
|
|
22385
|
+
fallbackCount: entry.fallbackCount,
|
|
22386
|
+
maxElapsedMs: entry.elapsed.length > 0 ? Math.max(...entry.elapsed) : undefined,
|
|
22387
|
+
provider,
|
|
22388
|
+
successCount: entry.successCount,
|
|
22389
|
+
timeoutCount: entry.timeoutCount
|
|
22390
|
+
})).sort((left, right) => right.eventCount - left.eventCount);
|
|
22391
|
+
};
|
|
22392
|
+
var summarizeVoiceTraceTimeline = (events, options = {}) => {
|
|
22393
|
+
const source = options.redact ? redactVoiceTraceEvents(events, options.redact) : events;
|
|
22394
|
+
const grouped = new Map;
|
|
22395
|
+
for (const event of filterVoiceTraceEvents(source)) {
|
|
22396
|
+
grouped.set(event.sessionId, [...grouped.get(event.sessionId) ?? [], event]);
|
|
22397
|
+
}
|
|
22398
|
+
const sessions = [...grouped.entries()].map(([sessionId, sessionEvents]) => {
|
|
22399
|
+
const sorted = filterVoiceTraceEvents(sessionEvents);
|
|
22400
|
+
const summary = summarizeVoiceTrace(sorted);
|
|
22401
|
+
const evaluation = evaluateVoiceTrace(sorted, options.evaluation);
|
|
22402
|
+
const startedAt = summary.startedAt ?? sorted[0]?.at ?? 0;
|
|
22403
|
+
const status = summary.failed ? "failed" : evaluation.issues.length > 0 ? "warning" : "healthy";
|
|
22404
|
+
return {
|
|
22405
|
+
endedAt: summary.endedAt,
|
|
22406
|
+
evaluation,
|
|
22407
|
+
events: sorted.map((event) => ({
|
|
22408
|
+
at: event.at,
|
|
22409
|
+
elapsedMs: eventElapsedMs(event),
|
|
22410
|
+
id: event.id,
|
|
22411
|
+
label: timelineLabel(event),
|
|
22412
|
+
offsetMs: Math.max(0, event.at - startedAt),
|
|
22413
|
+
provider: eventProvider(event),
|
|
22414
|
+
status: eventStatus(event),
|
|
22415
|
+
turnId: event.turnId,
|
|
22416
|
+
type: event.type
|
|
22417
|
+
})),
|
|
22418
|
+
lastEventAt: sorted.at(-1)?.at,
|
|
22419
|
+
providers: summarizeProviders(sorted),
|
|
22420
|
+
sessionId,
|
|
22421
|
+
startedAt: summary.startedAt,
|
|
22422
|
+
status,
|
|
22423
|
+
summary
|
|
22424
|
+
};
|
|
22425
|
+
}).sort((left, right) => (right.lastEventAt ?? 0) - (left.lastEventAt ?? 0)).slice(0, options.limit ?? 50);
|
|
22426
|
+
return {
|
|
22427
|
+
checkedAt: Date.now(),
|
|
22428
|
+
failed: sessions.filter((session) => session.status === "failed").length,
|
|
22429
|
+
sessions,
|
|
22430
|
+
total: sessions.length,
|
|
22431
|
+
warnings: sessions.filter((session) => session.status === "warning").length
|
|
22432
|
+
};
|
|
22433
|
+
};
|
|
22434
|
+
var formatMs3 = (value) => value === undefined ? "n/a" : `${String(value)}ms`;
|
|
22435
|
+
var renderProviderCards2 = (session) => session.providers.length === 0 ? '<p class="muted">No provider events recorded for this session.</p>' : `<div class="providers">${session.providers.map((provider) => `<article><strong>${escapeHtml38(provider.provider)}</strong><dl><div><dt>Events</dt><dd>${String(provider.eventCount)}</dd></div><div><dt>Avg</dt><dd>${formatMs3(provider.averageElapsedMs)}</dd></div><div><dt>Max</dt><dd>${formatMs3(provider.maxElapsedMs)}</dd></div><div><dt>Errors</dt><dd>${String(provider.errorCount)}</dd></div><div><dt>Fallbacks</dt><dd>${String(provider.fallbackCount)}</dd></div><div><dt>Timeouts</dt><dd>${String(provider.timeoutCount)}</dd></div></dl></article>`).join("")}</div>`;
|
|
22436
|
+
var renderVoiceTraceTimelineSessionHTML = (session, options = {}) => {
|
|
22437
|
+
const events = session.events.map((event) => `<tr class="${escapeHtml38(event.status ?? "")}"><td>+${String(event.offsetMs)}ms</td><td>${escapeHtml38(event.type)}</td><td>${escapeHtml38(event.label)}</td><td>${escapeHtml38(event.provider ?? "")}</td><td>${escapeHtml38(event.status ?? "")}</td><td>${formatMs3(event.elapsedMs)}</td></tr>`).join("");
|
|
22438
|
+
const issues = session.evaluation.issues.length ? session.evaluation.issues.map((issue) => `<li class="${escapeHtml38(issue.severity)}">${escapeHtml38(issue.code)}: ${escapeHtml38(issue.message)}</li>`).join("") : "<li>none</li>";
|
|
22439
|
+
return `<!doctype html><html lang="en"><head><meta charset="utf-8" /><meta name="viewport" content="width=device-width, initial-scale=1" /><title>${escapeHtml38(options.title ?? "Voice Trace Timeline")}</title><style>${timelineCSS}</style></head><body><main><a href="/traces">Back to traces</a><header><p class="eyebrow">Call timeline</p><h1>${escapeHtml38(session.sessionId)}</h1><p class="status ${escapeHtml38(session.status)}">${escapeHtml38(session.status)}</p></header><section class="metrics"><article><span>Events</span><strong>${String(session.summary.eventCount)}</strong></article><article><span>Turns</span><strong>${String(session.summary.turnCount)}</strong></article><article><span>Errors</span><strong>${String(session.summary.errorCount)}</strong></article><article><span>Duration</span><strong>${formatMs3(session.summary.callDurationMs)}</strong></article></section><section><h2>Providers</h2>${renderProviderCards2(session)}</section><section><h2>Issues</h2><ul>${issues}</ul></section><section><h2>Timeline</h2><table><thead><tr><th>Offset</th><th>Type</th><th>Event</th><th>Provider</th><th>Status</th><th>Latency</th></tr></thead><tbody>${events}</tbody></table></section></main></body></html>`;
|
|
22440
|
+
};
|
|
22441
|
+
var renderSessionRows = (report) => report.sessions.length === 0 ? '<tr><td colspan="7">No trace events recorded yet.</td></tr>' : report.sessions.map((session) => `<tr class="${escapeHtml38(session.status)}"><td><a href="/traces/${encodeURIComponent(session.sessionId)}">${escapeHtml38(session.sessionId)}</a></td><td>${escapeHtml38(session.status)}</td><td>${String(session.summary.eventCount)}</td><td>${String(session.summary.turnCount)}</td><td>${String(session.summary.errorCount)}</td><td>${formatMs3(session.summary.callDurationMs)}</td><td>${session.providers.map((provider) => escapeHtml38(provider.provider)).join(", ")}</td></tr>`).join("");
|
|
22442
|
+
var timelineCSS = "body{background:#0f1318;color:#f6f2e8;font-family:ui-sans-serif,system-ui,sans-serif;margin:0}main{margin:auto;max-width:1180px;padding:32px}a{color:#fbbf24}.eyebrow{color:#fbbf24;font-weight:900;letter-spacing:.12em;text-transform:uppercase}h1{font-size:clamp(2.4rem,6vw,4.5rem);line-height:.92;margin:.2rem 0 1rem}.status{border:1px solid #475569;border-radius:999px;display:inline-flex;padding:8px 12px}.healthy{color:#86efac}.warning{color:#fbbf24}.failed,.error{color:#fca5a5}.metrics,.providers{display:grid;gap:14px;grid-template-columns:repeat(auto-fit,minmax(170px,1fr));margin:20px 0}.metrics article,.providers article{background:#181f27;border:1px solid #2b3642;border-radius:20px;padding:16px}.metrics span,dt,.muted{color:#a8b0b8}.metrics strong{display:block;font-size:2rem}dl{display:grid;gap:8px;grid-template-columns:repeat(2,minmax(0,1fr));margin:12px 0 0}dd{font-weight:800;margin:4px 0 0}table{background:#181f27;border-collapse:collapse;border-radius:18px;overflow:hidden;width:100%}td,th{border-bottom:1px solid #2b3642;padding:12px;text-align:left}section{margin-top:28px}@media(max-width:760px){main{padding:20px}table{font-size:.9rem}}";
|
|
22443
|
+
var renderVoiceTraceTimelineHTML = (report, options = {}) => {
|
|
22444
|
+
const snippet = escapeHtml38(`const traceStore = createVoiceTraceSinkStore({
|
|
22445
|
+
store: runtimeStorage.traces,
|
|
22446
|
+
sinks: [
|
|
22447
|
+
createVoiceTraceHTTPSink({
|
|
22448
|
+
endpoint: process.env.VOICE_TRACE_WEBHOOK_URL
|
|
22449
|
+
})
|
|
22450
|
+
]
|
|
22451
|
+
});
|
|
22452
|
+
|
|
22453
|
+
app.use(
|
|
22454
|
+
createVoiceTraceTimelineRoutes({
|
|
22455
|
+
htmlPath: '/traces',
|
|
22456
|
+
path: '/api/voice-traces',
|
|
22457
|
+
redact: {
|
|
22458
|
+
keys: ['authorization', 'apiKey', 'token']
|
|
22459
|
+
},
|
|
22460
|
+
store: traceStore
|
|
22461
|
+
})
|
|
22462
|
+
);
|
|
22463
|
+
|
|
22464
|
+
app.use(
|
|
22465
|
+
createVoiceProductionReadinessRoutes({
|
|
22466
|
+
store: traceStore,
|
|
22467
|
+
traceDeliveries: runtimeStorage.traceDeliveries
|
|
22468
|
+
})
|
|
22469
|
+
);`);
|
|
22470
|
+
return `<!doctype html><html lang="en"><head><meta charset="utf-8" /><meta name="viewport" content="width=device-width, initial-scale=1" /><title>${escapeHtml38(options.title ?? "Voice Trace Timelines")}</title><style>${timelineCSS}.primitive{background:#181f27;border:1px solid #334155;border-radius:20px;margin:20px 0;padding:18px}.primitive p{line-height:1.55}.primitive pre{background:#0b1118;border:1px solid #2b3642;border-radius:16px;color:#dbeafe;overflow:auto;padding:14px}.primitive code{color:#bfdbfe}</style></head><body><main><header><p class="eyebrow">Self-hosted voice debugging</p><h1>${escapeHtml38(options.title ?? "Voice Trace Timelines")}</h1><p class="muted">Per-call event timelines with provider latency, fallback, timeout, handoff, and error context.</p></header><section class="metrics"><article><span>Sessions</span><strong>${String(report.total)}</strong></article><article><span>Failed</span><strong>${String(report.failed)}</strong></article><article><span>Warnings</span><strong>${String(report.warnings)}</strong></article></section><section class="primitive"><p class="eyebrow">Copy into your app</p><h2><code>createVoiceTraceTimelineRoutes(...)</code> makes traces the proof backbone</h2><p class="muted">Mount trace timelines from the same trace store used by readiness, simulations, provider recovery, delivery sinks, and phone-agent smoke proof.</p><pre><code>${snippet}</code></pre></section><table><thead><tr><th>Session</th><th>Status</th><th>Events</th><th>Turns</th><th>Errors</th><th>Duration</th><th>Providers</th></tr></thead><tbody>${renderSessionRows(report)}</tbody></table></main></body></html>`;
|
|
22471
|
+
};
|
|
22472
|
+
var createVoiceTraceTimelineRoutes = (options) => {
|
|
22473
|
+
const path = options.path ?? "/api/voice-traces";
|
|
22474
|
+
const htmlPath = options.htmlPath ?? "/traces";
|
|
22475
|
+
const title = options.title ?? "AbsoluteJS Voice Trace Timelines";
|
|
22476
|
+
const routes = new Elysia36({
|
|
22477
|
+
name: options.name ?? "absolutejs-voice-trace-timelines"
|
|
22478
|
+
});
|
|
22479
|
+
const buildReport = async () => summarizeVoiceTraceTimeline(await options.store.list(), {
|
|
22480
|
+
evaluation: options.evaluation,
|
|
22481
|
+
limit: options.limit,
|
|
22482
|
+
redact: options.redact
|
|
22483
|
+
});
|
|
22484
|
+
const findSession = async (sessionId) => {
|
|
22485
|
+
const report = summarizeVoiceTraceTimeline(await options.store.list({ sessionId }), {
|
|
22486
|
+
evaluation: options.evaluation,
|
|
22487
|
+
limit: 1,
|
|
22488
|
+
redact: options.redact
|
|
22489
|
+
});
|
|
22490
|
+
return report.sessions[0];
|
|
22491
|
+
};
|
|
22492
|
+
routes.get(path, async () => Response.json(await buildReport()));
|
|
22493
|
+
routes.get(`${path}/:sessionId`, async ({ params }) => {
|
|
22494
|
+
const session = await findSession(params.sessionId);
|
|
22495
|
+
return session ? Response.json(session) : Response.json({ error: "Voice trace session not found." }, { status: 404 });
|
|
22496
|
+
});
|
|
22497
|
+
routes.get(htmlPath, async () => {
|
|
22498
|
+
const report = await buildReport();
|
|
22499
|
+
const body = await (options.render ?? ((input) => renderVoiceTraceTimelineHTML(input, { title })))(report);
|
|
22500
|
+
return new Response(body, {
|
|
22501
|
+
headers: {
|
|
22502
|
+
"Content-Type": "text/html; charset=utf-8",
|
|
22503
|
+
...options.headers
|
|
22504
|
+
}
|
|
22505
|
+
});
|
|
22506
|
+
});
|
|
22507
|
+
routes.get(`${htmlPath}/:sessionId`, async ({ params }) => {
|
|
22508
|
+
const session = await findSession(params.sessionId);
|
|
22509
|
+
if (!session) {
|
|
22510
|
+
return Response.json({ error: "Voice trace session not found." }, { status: 404 });
|
|
22511
|
+
}
|
|
22512
|
+
const body = await (options.renderSession ?? ((input) => renderVoiceTraceTimelineSessionHTML(input, { title })))(session);
|
|
22513
|
+
return new Response(body, {
|
|
22514
|
+
headers: {
|
|
22515
|
+
"Content-Type": "text/html; charset=utf-8",
|
|
22516
|
+
...options.headers
|
|
22517
|
+
}
|
|
22518
|
+
});
|
|
22519
|
+
});
|
|
22520
|
+
return routes;
|
|
22521
|
+
};
|
|
22522
|
+
|
|
22523
|
+
// src/operationsRecord.ts
|
|
22524
|
+
var getString14 = (value) => typeof value === "string" ? value : undefined;
|
|
22525
|
+
var getNumber9 = (value) => typeof value === "number" && Number.isFinite(value) ? value : undefined;
|
|
22526
|
+
var countOutcome = (events, outcome) => events.filter((event) => event.outcome === outcome).length;
|
|
22527
|
+
var toHandoff = (event) => ({
|
|
22528
|
+
at: event.at,
|
|
22529
|
+
fromAgentId: getString14(event.payload.fromAgentId),
|
|
22530
|
+
metadata: event.payload.metadata && typeof event.payload.metadata === "object" && !Array.isArray(event.payload.metadata) ? event.payload.metadata : undefined,
|
|
22531
|
+
reason: getString14(event.payload.reason),
|
|
22532
|
+
status: getString14(event.payload.status),
|
|
22533
|
+
summary: getString14(event.payload.summary),
|
|
22534
|
+
targetAgentId: getString14(event.payload.targetAgentId),
|
|
22535
|
+
turnId: event.turnId
|
|
22536
|
+
});
|
|
22537
|
+
var toTool = (event) => ({
|
|
22538
|
+
at: event.at,
|
|
22539
|
+
elapsedMs: getNumber9(event.payload.elapsedMs),
|
|
22540
|
+
error: getString14(event.payload.error),
|
|
22541
|
+
status: getString14(event.payload.status),
|
|
22542
|
+
toolCallId: getString14(event.payload.toolCallId),
|
|
22543
|
+
toolName: getString14(event.payload.toolName),
|
|
22544
|
+
turnId: event.turnId
|
|
22545
|
+
});
|
|
22546
|
+
var resolveOutcome4 = (events) => {
|
|
22547
|
+
const agentResults = events.filter((event) => event.type === "agent.result");
|
|
22548
|
+
return {
|
|
22549
|
+
assistantReplies: events.filter((event) => event.type === "turn.assistant").length,
|
|
22550
|
+
complete: agentResults.some((event) => event.payload.complete === true),
|
|
22551
|
+
escalated: agentResults.some((event) => event.payload.escalated === true),
|
|
22552
|
+
noAnswer: agentResults.some((event) => event.payload.noAnswer === true),
|
|
22553
|
+
transferred: agentResults.some((event) => event.payload.transferred === true),
|
|
22554
|
+
voicemail: agentResults.some((event) => event.payload.voicemail === true)
|
|
22555
|
+
};
|
|
22556
|
+
};
|
|
22557
|
+
var buildVoiceOperationsRecord = async (options) => {
|
|
22558
|
+
const sourceEvents = options.events ?? await options.store?.list({ sessionId: options.sessionId }) ?? [];
|
|
22559
|
+
const traceEvents = filterVoiceTraceEvents(sourceEvents, {
|
|
22560
|
+
sessionId: options.sessionId
|
|
22561
|
+
});
|
|
22562
|
+
const timelineReport = summarizeVoiceTraceTimeline(traceEvents, {
|
|
22563
|
+
evaluation: options.evaluation,
|
|
22564
|
+
limit: 1,
|
|
22565
|
+
redact: options.redact
|
|
22566
|
+
});
|
|
22567
|
+
const timelineSession = timelineReport.sessions[0];
|
|
22568
|
+
const replay = await summarizeVoiceSessionReplay({
|
|
22569
|
+
evaluation: options.evaluation,
|
|
22570
|
+
events: traceEvents,
|
|
22571
|
+
redact: options.redact,
|
|
22572
|
+
sessionId: options.sessionId
|
|
22573
|
+
});
|
|
22574
|
+
const auditEvents = options.audit ? filterVoiceAuditEvents(await options.audit.list({ sessionId: options.sessionId })) : undefined;
|
|
22575
|
+
return {
|
|
22576
|
+
audit: auditEvents ? {
|
|
22577
|
+
error: countOutcome(auditEvents, "error"),
|
|
22578
|
+
events: auditEvents,
|
|
22579
|
+
skipped: countOutcome(auditEvents, "skipped"),
|
|
22580
|
+
success: countOutcome(auditEvents, "success"),
|
|
22581
|
+
total: auditEvents.length
|
|
22582
|
+
} : undefined,
|
|
22583
|
+
checkedAt: Date.now(),
|
|
22584
|
+
handoffs: traceEvents.filter((event) => event.type === "agent.handoff").map(toHandoff),
|
|
22585
|
+
outcome: resolveOutcome4(traceEvents),
|
|
22586
|
+
providers: timelineSession?.providers ?? [],
|
|
22587
|
+
replay,
|
|
22588
|
+
sessionId: options.sessionId,
|
|
22589
|
+
status: timelineSession?.status ?? "healthy",
|
|
22590
|
+
summary: timelineSession?.summary ?? replay.summary,
|
|
22591
|
+
timeline: timelineSession?.events ?? [],
|
|
22592
|
+
tools: traceEvents.filter((event) => event.type === "agent.tool").map(toTool),
|
|
22593
|
+
traceEvents
|
|
22594
|
+
};
|
|
22595
|
+
};
|
|
22596
|
+
var escapeHtml39 = (value) => value.replaceAll("&", "&").replaceAll("<", "<").replaceAll(">", ">").replaceAll('"', """).replaceAll("'", "'");
|
|
22597
|
+
var formatMs4 = (value) => value === undefined ? "n/a" : `${String(value)}ms`;
|
|
22598
|
+
var renderVoiceOperationsRecordHTML = (record, options = {}) => {
|
|
22599
|
+
const providers = record.providers.length ? record.providers.map((provider) => `<article><strong>${escapeHtml39(provider.provider)}</strong><span>${String(provider.eventCount)} events</span><span>${formatMs4(provider.averageElapsedMs)} avg</span><span>${String(provider.errorCount)} errors</span></article>`).join("") : '<p class="muted">No provider events recorded.</p>';
|
|
22600
|
+
const handoffs = record.handoffs.length ? record.handoffs.map((handoff) => `<li><strong>${escapeHtml39(handoff.fromAgentId ?? "unknown")}</strong> to <strong>${escapeHtml39(handoff.targetAgentId ?? "unknown")}</strong> <span>${escapeHtml39(handoff.status ?? "")}</span><p>${escapeHtml39(handoff.summary ?? handoff.reason ?? "")}</p></li>`).join("") : "<li>No agent handoffs recorded.</li>";
|
|
22601
|
+
const tools = record.tools.length ? record.tools.map((tool) => `<li><strong>${escapeHtml39(tool.toolName ?? "tool")}</strong> <span>${escapeHtml39(tool.status ?? "")}</span> ${formatMs4(tool.elapsedMs)} ${tool.error ? `<p>${escapeHtml39(tool.error)}</p>` : ""}</li>`).join("") : "<li>No tool calls recorded.</li>";
|
|
22602
|
+
const snippet = escapeHtml39(`app.use(
|
|
22603
|
+
createVoiceOperationsRecordRoutes({
|
|
22604
|
+
audit: auditStore,
|
|
22605
|
+
htmlPath: '/voice-ops/:sessionId',
|
|
22606
|
+
path: '/api/voice-ops/:sessionId',
|
|
22607
|
+
redact: {
|
|
22608
|
+
keys: ['authorization', 'apiKey', 'token']
|
|
22609
|
+
},
|
|
22610
|
+
store: traceStore
|
|
22611
|
+
})
|
|
22612
|
+
);`);
|
|
22613
|
+
return `<!doctype html><html lang="en"><head><meta charset="utf-8" /><meta name="viewport" content="width=device-width, initial-scale=1" /><title>${escapeHtml39(options.title ?? "Voice Operations Record")}</title><style>body{background:#101417;color:#f9f4e8;font-family:ui-sans-serif,system-ui,sans-serif;margin:0}main{margin:auto;max-width:1120px;padding:32px}.eyebrow{color:#fbbf24;font-size:.8rem;font-weight:900;letter-spacing:.14em;text-transform:uppercase}h1{font-size:clamp(2.4rem,6vw,4.8rem);line-height:.9;margin:.2rem 0 1rem}.status{border:1px solid #475569;border-radius:999px;display:inline-flex;padding:8px 12px}.healthy{color:#86efac}.warning{color:#fbbf24}.failed,.error{color:#fca5a5}.grid{display:grid;gap:14px;grid-template-columns:repeat(auto-fit,minmax(180px,1fr));margin:20px 0}.card,.primitive{background:#182025;border:1px solid #2d3a43;border-radius:20px;padding:16px}.card span,.muted{color:#a9b4bd}.card strong{display:block;font-size:2rem}section{margin-top:28px}article{display:grid;gap:8px}ul{display:grid;gap:10px;list-style:none;padding:0}li{background:#182025;border:1px solid #2d3a43;border-radius:16px;padding:14px}pre{background:#080d10;border:1px solid #2d3a43;border-radius:16px;color:#dbeafe;overflow:auto;padding:14px}</style></head><body><main><p class="eyebrow">Portable production proof</p><h1>${escapeHtml39(options.title ?? "Voice Operations Record")}</h1><p class="status ${escapeHtml39(record.status)}">${escapeHtml39(record.status)}</p><section class="grid"><div class="card"><span>Events</span><strong>${String(record.summary.eventCount)}</strong></div><div class="card"><span>Turns</span><strong>${String(record.summary.turnCount)}</strong></div><div class="card"><span>Errors</span><strong>${String(record.summary.errorCount)}</strong></div><div class="card"><span>Duration</span><strong>${formatMs4(record.summary.callDurationMs)}</strong></div><div class="card"><span>Audit</span><strong>${String(record.audit?.total ?? 0)}</strong></div></section><section class="primitive"><p class="eyebrow">Copy into your app</p><h2><code>createVoiceOperationsRecordRoutes(...)</code> gives every call one debuggable object</h2><p class="muted">Use this as the support/debug payload across traces, provider routing, tools, handoffs, audit, latency, and replay.</p><pre><code>${snippet}</code></pre></section><section><h2>Providers</h2><div class="grid">${providers}</div></section><section><h2>Handoffs</h2><ul>${handoffs}</ul></section><section><h2>Tools</h2><ul>${tools}</ul></section></main></body></html>`;
|
|
22614
|
+
};
|
|
22615
|
+
var createVoiceOperationsRecordRoutes = (options) => {
|
|
22616
|
+
const path = options.path ?? "/api/voice-operations/:sessionId";
|
|
22617
|
+
const htmlPath = options.htmlPath === undefined ? "/voice-operations/:sessionId" : options.htmlPath;
|
|
22618
|
+
const routes = new Elysia37({
|
|
22619
|
+
name: options.name ?? "absolutejs-voice-operations-record"
|
|
22620
|
+
});
|
|
22621
|
+
const buildRecord = (sessionId) => buildVoiceOperationsRecord({
|
|
22622
|
+
audit: options.audit,
|
|
22623
|
+
evaluation: options.evaluation,
|
|
22624
|
+
events: options.events,
|
|
22625
|
+
redact: options.redact,
|
|
22626
|
+
sessionId,
|
|
22627
|
+
store: options.store
|
|
22628
|
+
});
|
|
22629
|
+
const getSessionId = (params) => params.sessionId ?? "";
|
|
22630
|
+
routes.get(path, async ({ params }) => Response.json(await buildRecord(getSessionId(params))));
|
|
22631
|
+
if (htmlPath) {
|
|
22632
|
+
routes.get(htmlPath, async ({ params }) => {
|
|
22633
|
+
const record = await buildRecord(getSessionId(params));
|
|
22634
|
+
const body = await (options.render ?? ((input) => renderVoiceOperationsRecordHTML(input, {
|
|
22635
|
+
title: options.title
|
|
22636
|
+
})))(record);
|
|
22637
|
+
return new Response(body, {
|
|
22638
|
+
headers: {
|
|
22639
|
+
"Content-Type": "text/html; charset=utf-8",
|
|
22640
|
+
...options.headers
|
|
22641
|
+
}
|
|
22642
|
+
});
|
|
22643
|
+
});
|
|
22644
|
+
}
|
|
22645
|
+
return routes;
|
|
22646
|
+
};
|
|
22647
|
+
// src/opsStatus.ts
|
|
22648
|
+
var DEFAULT_LINKS2 = [
|
|
22649
|
+
{
|
|
22650
|
+
description: "Production quality gates.",
|
|
22651
|
+
href: "/quality",
|
|
22652
|
+
label: "Quality",
|
|
22653
|
+
statusHref: "/quality/status"
|
|
22654
|
+
},
|
|
22655
|
+
{
|
|
22656
|
+
description: "Replay sessions against evals and workflow contracts.",
|
|
22657
|
+
href: "/evals",
|
|
22658
|
+
label: "Evals",
|
|
22659
|
+
statusHref: "/evals/status"
|
|
22660
|
+
},
|
|
22661
|
+
{
|
|
22662
|
+
description: "Provider routing, fallback, and resilience controls.",
|
|
22663
|
+
href: "/resilience",
|
|
22664
|
+
label: "Resilience"
|
|
22665
|
+
},
|
|
22666
|
+
{
|
|
22667
|
+
description: "One JSON/HTML production readiness rollup.",
|
|
22668
|
+
href: "/production-readiness",
|
|
22669
|
+
label: "Production Readiness",
|
|
22670
|
+
statusHref: "/api/production-readiness"
|
|
22671
|
+
},
|
|
22672
|
+
{
|
|
22673
|
+
description: "Recent sessions and replay links.",
|
|
22674
|
+
href: "/sessions",
|
|
22675
|
+
label: "Sessions"
|
|
22676
|
+
},
|
|
22677
|
+
{
|
|
22678
|
+
description: "Trace-backed phone-agent production smoke proof.",
|
|
22679
|
+
href: "/voice/phone/smoke-contract",
|
|
22680
|
+
label: "Phone Smoke",
|
|
22681
|
+
statusHref: "/api/voice/phone/smoke-contract"
|
|
22682
|
+
}
|
|
22683
|
+
];
|
|
22684
|
+
var countStatus = (statuses) => ({
|
|
22685
|
+
failed: statuses.filter((status) => status === "fail").length,
|
|
22686
|
+
passed: statuses.filter((status) => status === "pass").length,
|
|
22687
|
+
total: statuses.length
|
|
22688
|
+
});
|
|
22689
|
+
var summarizeVoiceOpsStatus = async (options) => {
|
|
22690
|
+
const include = options.include;
|
|
22691
|
+
const shouldInclude = (surface) => include?.[surface] !== false;
|
|
22692
|
+
const evals = options.evals === false ? undefined : options.evals;
|
|
22693
|
+
const events = filterVoiceTraceEvents(await options.store.list());
|
|
22694
|
+
const [quality, workflows, providers, sessions, handoffs, deliverySinks] = await Promise.all([
|
|
22695
|
+
options.quality === false || !shouldInclude("quality") ? undefined : evaluateVoiceQuality({
|
|
22696
|
+
events,
|
|
22697
|
+
thresholds: options.quality?.thresholds
|
|
22698
|
+
}),
|
|
22699
|
+
!evals || !shouldInclude("workflows") ? undefined : (async () => {
|
|
22700
|
+
const fixtureReport = await runVoiceScenarioFixtureEvals({
|
|
22701
|
+
fixtures: evals.fixtures,
|
|
22702
|
+
fixtureStore: evals.fixtureStore,
|
|
22703
|
+
scenarios: evals.scenarios
|
|
22704
|
+
});
|
|
22705
|
+
if ((options.preferFixtureWorkflows ?? true) && fixtureReport.total > 0) {
|
|
22706
|
+
return {
|
|
22707
|
+
failed: fixtureReport.failed,
|
|
22708
|
+
source: "fixtures",
|
|
22709
|
+
status: fixtureReport.status,
|
|
22710
|
+
total: fixtureReport.total
|
|
22711
|
+
};
|
|
22712
|
+
}
|
|
22713
|
+
const liveReport = await runVoiceScenarioEvals({
|
|
22714
|
+
events,
|
|
22715
|
+
scenarios: evals.scenarios
|
|
22716
|
+
});
|
|
22717
|
+
return {
|
|
22718
|
+
failed: liveReport.failed,
|
|
22719
|
+
source: "live",
|
|
22720
|
+
status: liveReport.status,
|
|
22721
|
+
total: liveReport.total
|
|
22722
|
+
};
|
|
22723
|
+
})(),
|
|
22724
|
+
!shouldInclude("providers") ? undefined : Promise.all([
|
|
22725
|
+
summarizeVoiceProviderHealth({
|
|
22726
|
+
events,
|
|
22727
|
+
providers: options.llmProviders
|
|
22728
|
+
}),
|
|
22729
|
+
summarizeVoiceProviderHealth({
|
|
22730
|
+
events: events.filter((event) => event.payload.kind === "stt"),
|
|
22731
|
+
providers: options.sttProviders
|
|
22732
|
+
}),
|
|
22733
|
+
summarizeVoiceProviderHealth({
|
|
22734
|
+
events: events.filter((event) => event.payload.kind === "tts"),
|
|
22735
|
+
providers: options.ttsProviders
|
|
22736
|
+
})
|
|
22737
|
+
]).then((groups) => groups.flat()),
|
|
22738
|
+
!shouldInclude("sessions") ? undefined : summarizeVoiceSessions({
|
|
22739
|
+
events
|
|
22740
|
+
}),
|
|
22741
|
+
!shouldInclude("handoffs") ? undefined : summarizeVoiceHandoffHealth({
|
|
22742
|
+
events
|
|
22743
|
+
}),
|
|
22744
|
+
!options.deliverySinks || !shouldInclude("deliverySinks") ? undefined : buildVoiceDeliverySinkReport(options.deliverySinks)
|
|
22745
|
+
]);
|
|
22746
|
+
const providerRecovery = shouldInclude("providerRecovery") ? summarizeVoiceProviderFallbackRecovery(events) : undefined;
|
|
22747
|
+
const surfaces = {};
|
|
22748
|
+
const statuses = [];
|
|
22749
|
+
if (quality) {
|
|
22750
|
+
surfaces.quality = { status: quality.status };
|
|
22751
|
+
statuses.push(quality.status);
|
|
22752
|
+
}
|
|
22753
|
+
if (workflows) {
|
|
22754
|
+
surfaces.workflows = workflows;
|
|
22755
|
+
statuses.push(workflows.status);
|
|
22756
|
+
}
|
|
22757
|
+
if (providers) {
|
|
22758
|
+
const degraded = providers.filter((provider) => provider.status === "degraded" || provider.status === "rate-limited" || provider.status === "suppressed").length;
|
|
22759
|
+
const status = degraded > 0 ? "fail" : "pass";
|
|
22760
|
+
surfaces.providers = {
|
|
22761
|
+
degraded,
|
|
22762
|
+
status,
|
|
22763
|
+
total: providers.length
|
|
22764
|
+
};
|
|
22765
|
+
statuses.push(status);
|
|
22766
|
+
}
|
|
22767
|
+
if (providerRecovery) {
|
|
22768
|
+
surfaces.providerRecovery = providerRecovery;
|
|
22769
|
+
statuses.push(providerRecovery.status);
|
|
22770
|
+
}
|
|
22771
|
+
if (sessions) {
|
|
22315
22772
|
const failed = sessions.filter((session) => session.status === "failed").length;
|
|
22316
22773
|
const status = failed > 0 ? "fail" : "pass";
|
|
22317
22774
|
surfaces.sessions = {
|
|
@@ -22359,19 +22816,19 @@ var summarizeVoiceOpsStatus = async (options) => {
|
|
|
22359
22816
|
};
|
|
22360
22817
|
};
|
|
22361
22818
|
// src/opsStatusRoutes.ts
|
|
22362
|
-
import { Elysia as
|
|
22363
|
-
var
|
|
22819
|
+
import { Elysia as Elysia38 } from "elysia";
|
|
22820
|
+
var escapeHtml40 = (value) => value.replaceAll("&", "&").replaceAll("<", "<").replaceAll(">", ">").replaceAll('"', """).replaceAll("'", "'");
|
|
22364
22821
|
var renderVoiceOpsStatusHTML = (report, options = {}) => {
|
|
22365
22822
|
const title = options.title ?? "AbsoluteJS Voice Ops Status";
|
|
22366
22823
|
const surfaces = Object.entries(report.surfaces).map(([key, surface]) => {
|
|
22367
22824
|
const value = "recovered" in surface ? surface.total === 0 ? "0 events" : `${surface.recovered}/${surface.total}` : ("auditTotal" in surface) ? `${surface.auditTotal + surface.traceTotal} deliveries` : ("total" in surface) ? `${Math.max(surface.total - ("failed" in surface ? surface.failed : ("degraded" in surface) ? surface.degraded : 0), 0)}/${surface.total}` : surface.status;
|
|
22368
|
-
return `<article class="surface ${
|
|
22825
|
+
return `<article class="surface ${escapeHtml40(surface.status)}"><span>${escapeHtml40(surface.status.toUpperCase())}</span><h2>${escapeHtml40(key)}</h2><strong>${escapeHtml40(value)}</strong></article>`;
|
|
22369
22826
|
}).join("");
|
|
22370
|
-
return `<!doctype html><html lang="en"><head><meta charset="utf-8" /><meta name="viewport" content="width=device-width, initial-scale=1" /><title>${
|
|
22827
|
+
return `<!doctype html><html lang="en"><head><meta charset="utf-8" /><meta name="viewport" content="width=device-width, initial-scale=1" /><title>${escapeHtml40(title)}</title><style>body{background:#0d141b;color:#f8f3e7;font-family:ui-sans-serif,system-ui,sans-serif;margin:0}main{margin:auto;max-width:980px;padding:32px}.hero{background:linear-gradient(135deg,rgba(20,184,166,.2),rgba(245,158,11,.12));border:1px solid #283544;border-radius:28px;margin-bottom:18px;padding:28px}.eyebrow{color:#5eead4;font-weight:900;letter-spacing:.12em;text-transform:uppercase}h1{font-size:clamp(2.4rem,6vw,5rem);line-height:.9;margin:.2rem 0 1rem}.status{border:1px solid #3f3f46;border-radius:999px;display:inline-flex;font-weight:900;padding:8px 12px}.surfaces{display:grid;gap:14px;grid-template-columns:repeat(auto-fit,minmax(180px,1fr))}.surface{background:#151d26;border:1px solid #283544;border-radius:20px;padding:18px}.surface span{color:#aab5c0;font-size:.78rem;font-weight:900;letter-spacing:.08em}.surface strong{font-size:1.5rem}.pass{border-color:rgba(34,197,94,.55)}.fail{border-color:rgba(239,68,68,.75)}a{color:#5eead4}</style></head><body><main><section class="hero"><p class="eyebrow">Ops status</p><h1>${escapeHtml40(title)}</h1><p>Compact pass/fail status for framework widgets, demos, and small customer-facing health badges.</p><p class="status ${escapeHtml40(report.status)}">Overall: ${escapeHtml40(report.status.toUpperCase())}</p><p>${report.passed}/${report.total} checks passing</p></section><section class="surfaces">${surfaces || '<article class="surface pass"><span>PASS</span><h2>No checks configured</h2><strong>0/0</strong></article>'}</section></main></body></html>`;
|
|
22371
22828
|
};
|
|
22372
22829
|
var createVoiceOpsStatusRoutes = (options) => {
|
|
22373
22830
|
const path = options.path ?? "/api/voice/ops-status";
|
|
22374
|
-
const routes = new
|
|
22831
|
+
const routes = new Elysia38({
|
|
22375
22832
|
name: options.name ?? "absolutejs-voice-ops-status"
|
|
22376
22833
|
});
|
|
22377
22834
|
routes.get(path, async () => summarizeVoiceOpsStatus(options));
|
|
@@ -22804,10 +23261,10 @@ var createVoiceTTSProviderRouter = (options) => {
|
|
|
22804
23261
|
};
|
|
22805
23262
|
};
|
|
22806
23263
|
// src/traceDeliveryRoutes.ts
|
|
22807
|
-
import { Elysia as
|
|
22808
|
-
var
|
|
22809
|
-
var
|
|
22810
|
-
var
|
|
23264
|
+
import { Elysia as Elysia39 } from "elysia";
|
|
23265
|
+
var escapeHtml41 = (value) => value.replaceAll("&", "&").replaceAll("<", "<").replaceAll(">", ">").replaceAll('"', """).replaceAll("'", "'");
|
|
23266
|
+
var getString15 = (value) => typeof value === "string" && value.trim() ? value.trim() : undefined;
|
|
23267
|
+
var getNumber10 = (value) => {
|
|
22811
23268
|
if (typeof value === "number" && Number.isFinite(value)) {
|
|
22812
23269
|
return value;
|
|
22813
23270
|
}
|
|
@@ -22818,13 +23275,13 @@ var getNumber7 = (value) => {
|
|
|
22818
23275
|
return;
|
|
22819
23276
|
};
|
|
22820
23277
|
var parseStatus2 = (value) => {
|
|
22821
|
-
const text =
|
|
23278
|
+
const text = getString15(value);
|
|
22822
23279
|
return text === "pending" || text === "delivered" || text === "failed" || text === "skipped" || text === "all" ? text : undefined;
|
|
22823
23280
|
};
|
|
22824
23281
|
var resolveVoiceTraceDeliveryFilter = (query = {}, base = {}) => ({
|
|
22825
23282
|
...base,
|
|
22826
|
-
limit:
|
|
22827
|
-
q:
|
|
23283
|
+
limit: getNumber10(query.limit) ?? base.limit,
|
|
23284
|
+
q: getString15(query.q) ?? base.q,
|
|
22828
23285
|
status: parseStatus2(query.status) ?? base.status
|
|
22829
23286
|
});
|
|
22830
23287
|
var deliverySearchText2 = (delivery) => [
|
|
@@ -22886,14 +23343,14 @@ var renderSinkResults2 = (delivery) => {
|
|
|
22886
23343
|
if (entries.length === 0) {
|
|
22887
23344
|
return "<p>No sink delivery attempts recorded yet.</p>";
|
|
22888
23345
|
}
|
|
22889
|
-
return `<ul>${entries.map(([sinkId, result]) => `<li><strong>${
|
|
23346
|
+
return `<ul>${entries.map(([sinkId, result]) => `<li><strong>${escapeHtml41(sinkId)}</strong>: ${escapeHtml41(result.status)}${result.deliveredTo ? ` to ${escapeHtml41(result.deliveredTo)}` : ""}${result.error ? ` (${escapeHtml41(result.error)})` : ""}</li>`).join("")}</ul>`;
|
|
22890
23347
|
};
|
|
22891
|
-
var renderEventList2 = (delivery) => delivery.events.length === 0 ? "<p>No trace events in this delivery.</p>" : `<ul>${delivery.events.map((event) => `<li>${
|
|
23348
|
+
var renderEventList2 = (delivery) => delivery.events.length === 0 ? "<p>No trace events in this delivery.</p>" : `<ul>${delivery.events.map((event) => `<li>${escapeHtml41(event.type)} <small>${escapeHtml41(event.id)}</small>${event.sessionId ? ` session=${escapeHtml41(event.sessionId)}` : ""}</li>`).join("")}</ul>`;
|
|
22892
23349
|
var renderVoiceTraceDeliveryHTML = (report, options = {}) => {
|
|
22893
23350
|
const title = options.title ?? "AbsoluteJS Voice Trace Deliveries";
|
|
22894
|
-
const drainAction = options.workerPath === false ? "" : `<form method="post" action="${
|
|
22895
|
-
const rows = report.deliveries.map((delivery) => `<article class="delivery ${
|
|
22896
|
-
return `<!doctype html><html lang="en"><head><meta charset="utf-8" /><meta name="viewport" content="width=device-width, initial-scale=1" /><title>${
|
|
23351
|
+
const drainAction = options.workerPath === false ? "" : `<form method="post" action="${escapeHtml41(options.workerPath ?? "/api/voice-trace-deliveries/drain")}"><button type="submit">Drain trace deliveries</button></form>`;
|
|
23352
|
+
const rows = report.deliveries.map((delivery) => `<article class="delivery ${escapeHtml41(delivery.deliveryStatus)}"><div class="head"><div><span>${escapeHtml41(delivery.deliveryStatus)}</span><h2>${escapeHtml41(delivery.id)}</h2><p>${escapeHtml41(new Date(delivery.createdAt).toLocaleString())}${delivery.deliveredAt ? ` \xB7 delivered ${escapeHtml41(new Date(delivery.deliveredAt).toLocaleString())}` : ""}</p></div><strong>${String(delivery.deliveryAttempts ?? 0)} attempt(s)</strong></div>${delivery.deliveryError ? `<p class="error">${escapeHtml41(delivery.deliveryError)}</p>` : ""}<h3>Sinks</h3>${renderSinkResults2(delivery)}<h3>Events</h3>${renderEventList2(delivery)}</article>`).join("");
|
|
23353
|
+
return `<!doctype html><html lang="en"><head><meta charset="utf-8" /><meta name="viewport" content="width=device-width, initial-scale=1" /><title>${escapeHtml41(title)}</title><style>body{background:#0f1318;color:#f4efe1;font-family:ui-sans-serif,system-ui,sans-serif;margin:0}main{margin:auto;max-width:1120px;padding:32px}.hero{background:linear-gradient(135deg,rgba(34,197,94,.16),rgba(14,165,233,.14));border:1px solid #26313d;border-radius:28px;margin-bottom:18px;padding:28px}.eyebrow{color:#86efac;font-weight:900;letter-spacing:.12em;text-transform:uppercase}h1{font-size:clamp(2.2rem,5vw,4.8rem);line-height:.92;margin:.2rem 0 1rem}.grid{display:grid;gap:12px;grid-template-columns:repeat(4,1fr);margin-bottom:16px}.grid article,.delivery{background:#151b22;border:1px solid #26313d;border-radius:22px;padding:18px}.grid span,.delivery span{color:#86efac;font-size:.78rem;font-weight:900;letter-spacing:.08em;text-transform:uppercase}.grid strong{display:block;font-size:2rem}.deliveries{display:grid;gap:14px}.delivery.failed{border-color:rgba(239,68,68,.75)}.delivery.pending{border-color:rgba(245,158,11,.7)}.delivery.delivered{border-color:rgba(34,197,94,.55)}.delivery.skipped{border-color:rgba(148,163,184,.6)}.head{align-items:start;display:flex;gap:14px;justify-content:space-between}.delivery h2{font-size:1.05rem;margin:.3rem 0;overflow-wrap:anywhere}.delivery h3{margin:1rem 0 .3rem}.delivery p,.delivery li{color:#c8d0d8}.error{color:#fecaca!important}button{background:#86efac;border:0;border-radius:999px;color:#07111f;cursor:pointer;font-weight:900;margin-top:12px;padding:10px 14px}@media(max-width:760px){main{padding:20px}.grid{grid-template-columns:1fr 1fr}.head{display:block}}</style></head><body><main><section class="hero"><p class="eyebrow">Trace export health</p><h1>${escapeHtml41(title)}</h1><p>Checked ${escapeHtml41(new Date(report.checkedAt).toLocaleString())}. Showing ${String(report.deliveries.length)} delivery item(s).</p>${drainAction}</section>${renderMetricGrid3(report)}<section class="deliveries">${rows || "<p>No trace deliveries match this filter.</p>"}</section></main></body></html>`;
|
|
22897
23354
|
};
|
|
22898
23355
|
var createVoiceTraceDeliveryJSONHandler = (options) => async ({ query }) => buildVoiceTraceDeliveryReport(options, resolveVoiceTraceDeliveryFilter(query, options.filter));
|
|
22899
23356
|
var createVoiceTraceDeliveryHTMLHandler = (options) => async ({ query }) => {
|
|
@@ -22913,7 +23370,7 @@ var createVoiceTraceDeliveryRoutes = (options) => {
|
|
|
22913
23370
|
const path = options.path ?? "/api/voice-trace-deliveries";
|
|
22914
23371
|
const htmlPath = options.htmlPath === undefined ? "/traces/deliveries" : options.htmlPath;
|
|
22915
23372
|
const workerPath = options.workerPath === undefined ? `${path}/drain` : options.workerPath;
|
|
22916
|
-
const routes = new
|
|
23373
|
+
const routes = new Elysia39({
|
|
22917
23374
|
name: options.name ?? "absolutejs-voice-trace-deliveries"
|
|
22918
23375
|
}).get(path, createVoiceTraceDeliveryJSONHandler(options));
|
|
22919
23376
|
if (htmlPath !== false) {
|
|
@@ -22930,263 +23387,6 @@ var createVoiceTraceDeliveryRoutes = (options) => {
|
|
|
22930
23387
|
}
|
|
22931
23388
|
return routes;
|
|
22932
23389
|
};
|
|
22933
|
-
// src/traceTimeline.ts
|
|
22934
|
-
import { Elysia as Elysia38 } from "elysia";
|
|
22935
|
-
var escapeHtml40 = (value) => value.replaceAll("&", "&").replaceAll("<", "<").replaceAll(">", ">").replaceAll('"', """).replaceAll("'", "'");
|
|
22936
|
-
var getString13 = (value) => typeof value === "string" && value.trim() ? value : undefined;
|
|
22937
|
-
var getNumber8 = (value) => typeof value === "number" && Number.isFinite(value) ? value : undefined;
|
|
22938
|
-
var firstString3 = (payload, keys) => {
|
|
22939
|
-
for (const key of keys) {
|
|
22940
|
-
const value = getString13(payload[key]);
|
|
22941
|
-
if (value) {
|
|
22942
|
-
return value;
|
|
22943
|
-
}
|
|
22944
|
-
}
|
|
22945
|
-
return;
|
|
22946
|
-
};
|
|
22947
|
-
var firstNumber3 = (payload, keys) => {
|
|
22948
|
-
for (const key of keys) {
|
|
22949
|
-
const value = getNumber8(payload[key]);
|
|
22950
|
-
if (value !== undefined) {
|
|
22951
|
-
return value;
|
|
22952
|
-
}
|
|
22953
|
-
}
|
|
22954
|
-
return;
|
|
22955
|
-
};
|
|
22956
|
-
var eventProvider = (event) => firstString3(event.payload, [
|
|
22957
|
-
"provider",
|
|
22958
|
-
"selectedProvider",
|
|
22959
|
-
"fallbackProvider",
|
|
22960
|
-
"variantId"
|
|
22961
|
-
]);
|
|
22962
|
-
var eventStatus = (event) => firstString3(event.payload, [
|
|
22963
|
-
"providerStatus",
|
|
22964
|
-
"status",
|
|
22965
|
-
"disposition",
|
|
22966
|
-
"type",
|
|
22967
|
-
"reason"
|
|
22968
|
-
]);
|
|
22969
|
-
var eventElapsedMs = (event) => firstNumber3(event.payload, ["elapsedMs", "latencyMs", "durationMs"]);
|
|
22970
|
-
var timelineLabel = (event) => {
|
|
22971
|
-
switch (event.type) {
|
|
22972
|
-
case "call.lifecycle":
|
|
22973
|
-
return `Call ${eventStatus(event) ?? "lifecycle"}`;
|
|
22974
|
-
case "turn.transcript":
|
|
22975
|
-
return event.payload.isFinal === true ? "Final transcript" : "Partial transcript";
|
|
22976
|
-
case "turn.committed":
|
|
22977
|
-
return `Committed turn${getString13(event.payload.reason) ? ` (${getString13(event.payload.reason)})` : ""}`;
|
|
22978
|
-
case "turn.assistant":
|
|
22979
|
-
return "Assistant reply";
|
|
22980
|
-
case "agent.model":
|
|
22981
|
-
return `Model call${eventProvider(event) ? ` via ${eventProvider(event)}` : ""}`;
|
|
22982
|
-
case "agent.tool":
|
|
22983
|
-
return `Tool ${getString13(event.payload.toolName) ?? "call"}`;
|
|
22984
|
-
case "agent.handoff":
|
|
22985
|
-
return `Agent handoff${getString13(event.payload.targetAgentId) ? ` to ${getString13(event.payload.targetAgentId)}` : ""}`;
|
|
22986
|
-
case "assistant.run":
|
|
22987
|
-
return `Assistant run${eventProvider(event) ? ` via ${eventProvider(event)}` : ""}`;
|
|
22988
|
-
case "assistant.guardrail":
|
|
22989
|
-
return `Guardrail ${eventStatus(event) ?? "check"}`;
|
|
22990
|
-
case "call.handoff":
|
|
22991
|
-
return `Call handoff ${eventStatus(event) ?? ""}`.trim();
|
|
22992
|
-
case "client.live_latency":
|
|
22993
|
-
return `Live latency${eventElapsedMs(event) !== undefined ? ` ${eventElapsedMs(event)}ms` : ""}`;
|
|
22994
|
-
case "session.error":
|
|
22995
|
-
return `Error${getString13(event.payload.error) ? `: ${getString13(event.payload.error)}` : ""}`;
|
|
22996
|
-
case "turn.cost":
|
|
22997
|
-
return "Cost telemetry";
|
|
22998
|
-
case "turn_latency.stage":
|
|
22999
|
-
return `Latency ${getString13(event.payload.stage) ?? "stage"}`;
|
|
23000
|
-
case "workflow.contract":
|
|
23001
|
-
return `Workflow contract ${eventStatus(event) ?? ""}`.trim();
|
|
23002
|
-
default:
|
|
23003
|
-
return event.type;
|
|
23004
|
-
}
|
|
23005
|
-
};
|
|
23006
|
-
var summarizeProviders = (events) => {
|
|
23007
|
-
const entries = new Map;
|
|
23008
|
-
const getEntry = (provider) => {
|
|
23009
|
-
const existing = entries.get(provider);
|
|
23010
|
-
if (existing) {
|
|
23011
|
-
return existing;
|
|
23012
|
-
}
|
|
23013
|
-
const entry = {
|
|
23014
|
-
elapsed: [],
|
|
23015
|
-
errorCount: 0,
|
|
23016
|
-
eventCount: 0,
|
|
23017
|
-
fallbackCount: 0,
|
|
23018
|
-
successCount: 0,
|
|
23019
|
-
timeoutCount: 0
|
|
23020
|
-
};
|
|
23021
|
-
entries.set(provider, entry);
|
|
23022
|
-
return entry;
|
|
23023
|
-
};
|
|
23024
|
-
for (const event of events) {
|
|
23025
|
-
const provider = eventProvider(event);
|
|
23026
|
-
if (!provider) {
|
|
23027
|
-
continue;
|
|
23028
|
-
}
|
|
23029
|
-
const entry = getEntry(provider);
|
|
23030
|
-
const status = eventStatus(event);
|
|
23031
|
-
const elapsedMs = eventElapsedMs(event);
|
|
23032
|
-
entry.eventCount += 1;
|
|
23033
|
-
if (elapsedMs !== undefined) {
|
|
23034
|
-
entry.elapsed.push(elapsedMs);
|
|
23035
|
-
}
|
|
23036
|
-
if (status === "success") {
|
|
23037
|
-
entry.successCount += 1;
|
|
23038
|
-
}
|
|
23039
|
-
if (status === "fallback") {
|
|
23040
|
-
entry.fallbackCount += 1;
|
|
23041
|
-
}
|
|
23042
|
-
if (status === "error") {
|
|
23043
|
-
entry.errorCount += 1;
|
|
23044
|
-
}
|
|
23045
|
-
if (status === "timeout") {
|
|
23046
|
-
entry.timeoutCount += 1;
|
|
23047
|
-
}
|
|
23048
|
-
}
|
|
23049
|
-
return [...entries.entries()].map(([provider, entry]) => ({
|
|
23050
|
-
averageElapsedMs: entry.elapsed.length > 0 ? Math.round(entry.elapsed.reduce((total, value) => total + value, 0) / entry.elapsed.length) : undefined,
|
|
23051
|
-
errorCount: entry.errorCount,
|
|
23052
|
-
eventCount: entry.eventCount,
|
|
23053
|
-
fallbackCount: entry.fallbackCount,
|
|
23054
|
-
maxElapsedMs: entry.elapsed.length > 0 ? Math.max(...entry.elapsed) : undefined,
|
|
23055
|
-
provider,
|
|
23056
|
-
successCount: entry.successCount,
|
|
23057
|
-
timeoutCount: entry.timeoutCount
|
|
23058
|
-
})).sort((left, right) => right.eventCount - left.eventCount);
|
|
23059
|
-
};
|
|
23060
|
-
var summarizeVoiceTraceTimeline = (events, options = {}) => {
|
|
23061
|
-
const source = options.redact ? redactVoiceTraceEvents(events, options.redact) : events;
|
|
23062
|
-
const grouped = new Map;
|
|
23063
|
-
for (const event of filterVoiceTraceEvents(source)) {
|
|
23064
|
-
grouped.set(event.sessionId, [...grouped.get(event.sessionId) ?? [], event]);
|
|
23065
|
-
}
|
|
23066
|
-
const sessions = [...grouped.entries()].map(([sessionId, sessionEvents]) => {
|
|
23067
|
-
const sorted = filterVoiceTraceEvents(sessionEvents);
|
|
23068
|
-
const summary = summarizeVoiceTrace(sorted);
|
|
23069
|
-
const evaluation = evaluateVoiceTrace(sorted, options.evaluation);
|
|
23070
|
-
const startedAt = summary.startedAt ?? sorted[0]?.at ?? 0;
|
|
23071
|
-
const status = summary.failed ? "failed" : evaluation.issues.length > 0 ? "warning" : "healthy";
|
|
23072
|
-
return {
|
|
23073
|
-
endedAt: summary.endedAt,
|
|
23074
|
-
evaluation,
|
|
23075
|
-
events: sorted.map((event) => ({
|
|
23076
|
-
at: event.at,
|
|
23077
|
-
elapsedMs: eventElapsedMs(event),
|
|
23078
|
-
id: event.id,
|
|
23079
|
-
label: timelineLabel(event),
|
|
23080
|
-
offsetMs: Math.max(0, event.at - startedAt),
|
|
23081
|
-
provider: eventProvider(event),
|
|
23082
|
-
status: eventStatus(event),
|
|
23083
|
-
turnId: event.turnId,
|
|
23084
|
-
type: event.type
|
|
23085
|
-
})),
|
|
23086
|
-
lastEventAt: sorted.at(-1)?.at,
|
|
23087
|
-
providers: summarizeProviders(sorted),
|
|
23088
|
-
sessionId,
|
|
23089
|
-
startedAt: summary.startedAt,
|
|
23090
|
-
status,
|
|
23091
|
-
summary
|
|
23092
|
-
};
|
|
23093
|
-
}).sort((left, right) => (right.lastEventAt ?? 0) - (left.lastEventAt ?? 0)).slice(0, options.limit ?? 50);
|
|
23094
|
-
return {
|
|
23095
|
-
checkedAt: Date.now(),
|
|
23096
|
-
failed: sessions.filter((session) => session.status === "failed").length,
|
|
23097
|
-
sessions,
|
|
23098
|
-
total: sessions.length,
|
|
23099
|
-
warnings: sessions.filter((session) => session.status === "warning").length
|
|
23100
|
-
};
|
|
23101
|
-
};
|
|
23102
|
-
var formatMs3 = (value) => value === undefined ? "n/a" : `${String(value)}ms`;
|
|
23103
|
-
var renderProviderCards2 = (session) => session.providers.length === 0 ? '<p class="muted">No provider events recorded for this session.</p>' : `<div class="providers">${session.providers.map((provider) => `<article><strong>${escapeHtml40(provider.provider)}</strong><dl><div><dt>Events</dt><dd>${String(provider.eventCount)}</dd></div><div><dt>Avg</dt><dd>${formatMs3(provider.averageElapsedMs)}</dd></div><div><dt>Max</dt><dd>${formatMs3(provider.maxElapsedMs)}</dd></div><div><dt>Errors</dt><dd>${String(provider.errorCount)}</dd></div><div><dt>Fallbacks</dt><dd>${String(provider.fallbackCount)}</dd></div><div><dt>Timeouts</dt><dd>${String(provider.timeoutCount)}</dd></div></dl></article>`).join("")}</div>`;
|
|
23104
|
-
var renderVoiceTraceTimelineSessionHTML = (session, options = {}) => {
|
|
23105
|
-
const events = session.events.map((event) => `<tr class="${escapeHtml40(event.status ?? "")}"><td>+${String(event.offsetMs)}ms</td><td>${escapeHtml40(event.type)}</td><td>${escapeHtml40(event.label)}</td><td>${escapeHtml40(event.provider ?? "")}</td><td>${escapeHtml40(event.status ?? "")}</td><td>${formatMs3(event.elapsedMs)}</td></tr>`).join("");
|
|
23106
|
-
const issues = session.evaluation.issues.length ? session.evaluation.issues.map((issue) => `<li class="${escapeHtml40(issue.severity)}">${escapeHtml40(issue.code)}: ${escapeHtml40(issue.message)}</li>`).join("") : "<li>none</li>";
|
|
23107
|
-
return `<!doctype html><html lang="en"><head><meta charset="utf-8" /><meta name="viewport" content="width=device-width, initial-scale=1" /><title>${escapeHtml40(options.title ?? "Voice Trace Timeline")}</title><style>${timelineCSS}</style></head><body><main><a href="/traces">Back to traces</a><header><p class="eyebrow">Call timeline</p><h1>${escapeHtml40(session.sessionId)}</h1><p class="status ${escapeHtml40(session.status)}">${escapeHtml40(session.status)}</p></header><section class="metrics"><article><span>Events</span><strong>${String(session.summary.eventCount)}</strong></article><article><span>Turns</span><strong>${String(session.summary.turnCount)}</strong></article><article><span>Errors</span><strong>${String(session.summary.errorCount)}</strong></article><article><span>Duration</span><strong>${formatMs3(session.summary.callDurationMs)}</strong></article></section><section><h2>Providers</h2>${renderProviderCards2(session)}</section><section><h2>Issues</h2><ul>${issues}</ul></section><section><h2>Timeline</h2><table><thead><tr><th>Offset</th><th>Type</th><th>Event</th><th>Provider</th><th>Status</th><th>Latency</th></tr></thead><tbody>${events}</tbody></table></section></main></body></html>`;
|
|
23108
|
-
};
|
|
23109
|
-
var renderSessionRows = (report) => report.sessions.length === 0 ? '<tr><td colspan="7">No trace events recorded yet.</td></tr>' : report.sessions.map((session) => `<tr class="${escapeHtml40(session.status)}"><td><a href="/traces/${encodeURIComponent(session.sessionId)}">${escapeHtml40(session.sessionId)}</a></td><td>${escapeHtml40(session.status)}</td><td>${String(session.summary.eventCount)}</td><td>${String(session.summary.turnCount)}</td><td>${String(session.summary.errorCount)}</td><td>${formatMs3(session.summary.callDurationMs)}</td><td>${session.providers.map((provider) => escapeHtml40(provider.provider)).join(", ")}</td></tr>`).join("");
|
|
23110
|
-
var timelineCSS = "body{background:#0f1318;color:#f6f2e8;font-family:ui-sans-serif,system-ui,sans-serif;margin:0}main{margin:auto;max-width:1180px;padding:32px}a{color:#fbbf24}.eyebrow{color:#fbbf24;font-weight:900;letter-spacing:.12em;text-transform:uppercase}h1{font-size:clamp(2.4rem,6vw,4.5rem);line-height:.92;margin:.2rem 0 1rem}.status{border:1px solid #475569;border-radius:999px;display:inline-flex;padding:8px 12px}.healthy{color:#86efac}.warning{color:#fbbf24}.failed,.error{color:#fca5a5}.metrics,.providers{display:grid;gap:14px;grid-template-columns:repeat(auto-fit,minmax(170px,1fr));margin:20px 0}.metrics article,.providers article{background:#181f27;border:1px solid #2b3642;border-radius:20px;padding:16px}.metrics span,dt,.muted{color:#a8b0b8}.metrics strong{display:block;font-size:2rem}dl{display:grid;gap:8px;grid-template-columns:repeat(2,minmax(0,1fr));margin:12px 0 0}dd{font-weight:800;margin:4px 0 0}table{background:#181f27;border-collapse:collapse;border-radius:18px;overflow:hidden;width:100%}td,th{border-bottom:1px solid #2b3642;padding:12px;text-align:left}section{margin-top:28px}@media(max-width:760px){main{padding:20px}table{font-size:.9rem}}";
|
|
23111
|
-
var renderVoiceTraceTimelineHTML = (report, options = {}) => {
|
|
23112
|
-
const snippet = escapeHtml40(`const traceStore = createVoiceTraceSinkStore({
|
|
23113
|
-
store: runtimeStorage.traces,
|
|
23114
|
-
sinks: [
|
|
23115
|
-
createVoiceTraceHTTPSink({
|
|
23116
|
-
endpoint: process.env.VOICE_TRACE_WEBHOOK_URL
|
|
23117
|
-
})
|
|
23118
|
-
]
|
|
23119
|
-
});
|
|
23120
|
-
|
|
23121
|
-
app.use(
|
|
23122
|
-
createVoiceTraceTimelineRoutes({
|
|
23123
|
-
htmlPath: '/traces',
|
|
23124
|
-
path: '/api/voice-traces',
|
|
23125
|
-
redact: {
|
|
23126
|
-
keys: ['authorization', 'apiKey', 'token']
|
|
23127
|
-
},
|
|
23128
|
-
store: traceStore
|
|
23129
|
-
})
|
|
23130
|
-
);
|
|
23131
|
-
|
|
23132
|
-
app.use(
|
|
23133
|
-
createVoiceProductionReadinessRoutes({
|
|
23134
|
-
store: traceStore,
|
|
23135
|
-
traceDeliveries: runtimeStorage.traceDeliveries
|
|
23136
|
-
})
|
|
23137
|
-
);`);
|
|
23138
|
-
return `<!doctype html><html lang="en"><head><meta charset="utf-8" /><meta name="viewport" content="width=device-width, initial-scale=1" /><title>${escapeHtml40(options.title ?? "Voice Trace Timelines")}</title><style>${timelineCSS}.primitive{background:#181f27;border:1px solid #334155;border-radius:20px;margin:20px 0;padding:18px}.primitive p{line-height:1.55}.primitive pre{background:#0b1118;border:1px solid #2b3642;border-radius:16px;color:#dbeafe;overflow:auto;padding:14px}.primitive code{color:#bfdbfe}</style></head><body><main><header><p class="eyebrow">Self-hosted voice debugging</p><h1>${escapeHtml40(options.title ?? "Voice Trace Timelines")}</h1><p class="muted">Per-call event timelines with provider latency, fallback, timeout, handoff, and error context.</p></header><section class="metrics"><article><span>Sessions</span><strong>${String(report.total)}</strong></article><article><span>Failed</span><strong>${String(report.failed)}</strong></article><article><span>Warnings</span><strong>${String(report.warnings)}</strong></article></section><section class="primitive"><p class="eyebrow">Copy into your app</p><h2><code>createVoiceTraceTimelineRoutes(...)</code> makes traces the proof backbone</h2><p class="muted">Mount trace timelines from the same trace store used by readiness, simulations, provider recovery, delivery sinks, and phone-agent smoke proof.</p><pre><code>${snippet}</code></pre></section><table><thead><tr><th>Session</th><th>Status</th><th>Events</th><th>Turns</th><th>Errors</th><th>Duration</th><th>Providers</th></tr></thead><tbody>${renderSessionRows(report)}</tbody></table></main></body></html>`;
|
|
23139
|
-
};
|
|
23140
|
-
var createVoiceTraceTimelineRoutes = (options) => {
|
|
23141
|
-
const path = options.path ?? "/api/voice-traces";
|
|
23142
|
-
const htmlPath = options.htmlPath ?? "/traces";
|
|
23143
|
-
const title = options.title ?? "AbsoluteJS Voice Trace Timelines";
|
|
23144
|
-
const routes = new Elysia38({
|
|
23145
|
-
name: options.name ?? "absolutejs-voice-trace-timelines"
|
|
23146
|
-
});
|
|
23147
|
-
const buildReport = async () => summarizeVoiceTraceTimeline(await options.store.list(), {
|
|
23148
|
-
evaluation: options.evaluation,
|
|
23149
|
-
limit: options.limit,
|
|
23150
|
-
redact: options.redact
|
|
23151
|
-
});
|
|
23152
|
-
const findSession = async (sessionId) => {
|
|
23153
|
-
const report = summarizeVoiceTraceTimeline(await options.store.list({ sessionId }), {
|
|
23154
|
-
evaluation: options.evaluation,
|
|
23155
|
-
limit: 1,
|
|
23156
|
-
redact: options.redact
|
|
23157
|
-
});
|
|
23158
|
-
return report.sessions[0];
|
|
23159
|
-
};
|
|
23160
|
-
routes.get(path, async () => Response.json(await buildReport()));
|
|
23161
|
-
routes.get(`${path}/:sessionId`, async ({ params }) => {
|
|
23162
|
-
const session = await findSession(params.sessionId);
|
|
23163
|
-
return session ? Response.json(session) : Response.json({ error: "Voice trace session not found." }, { status: 404 });
|
|
23164
|
-
});
|
|
23165
|
-
routes.get(htmlPath, async () => {
|
|
23166
|
-
const report = await buildReport();
|
|
23167
|
-
const body = await (options.render ?? ((input) => renderVoiceTraceTimelineHTML(input, { title })))(report);
|
|
23168
|
-
return new Response(body, {
|
|
23169
|
-
headers: {
|
|
23170
|
-
"Content-Type": "text/html; charset=utf-8",
|
|
23171
|
-
...options.headers
|
|
23172
|
-
}
|
|
23173
|
-
});
|
|
23174
|
-
});
|
|
23175
|
-
routes.get(`${htmlPath}/:sessionId`, async ({ params }) => {
|
|
23176
|
-
const session = await findSession(params.sessionId);
|
|
23177
|
-
if (!session) {
|
|
23178
|
-
return Response.json({ error: "Voice trace session not found." }, { status: 404 });
|
|
23179
|
-
}
|
|
23180
|
-
const body = await (options.renderSession ?? ((input) => renderVoiceTraceTimelineSessionHTML(input, { title })))(session);
|
|
23181
|
-
return new Response(body, {
|
|
23182
|
-
headers: {
|
|
23183
|
-
"Content-Type": "text/html; charset=utf-8",
|
|
23184
|
-
...options.headers
|
|
23185
|
-
}
|
|
23186
|
-
});
|
|
23187
|
-
});
|
|
23188
|
-
return routes;
|
|
23189
|
-
};
|
|
23190
23390
|
// src/sqliteStore.ts
|
|
23191
23391
|
import { Database } from "bun:sqlite";
|
|
23192
23392
|
var normalizeTableNameSegment = (value) => value.trim().replace(/[^a-zA-Z0-9_]+/g, "_").replace(/^_+|_+$/g, "") || "voice";
|
|
@@ -23794,7 +23994,7 @@ var createVoiceMemoryStore = () => {
|
|
|
23794
23994
|
return { get, getOrCreate, list, remove, set };
|
|
23795
23995
|
};
|
|
23796
23996
|
// src/opsWebhook.ts
|
|
23797
|
-
import { Elysia as
|
|
23997
|
+
import { Elysia as Elysia40 } from "elysia";
|
|
23798
23998
|
var toHex6 = (bytes) => Array.from(bytes, (byte) => byte.toString(16).padStart(2, "0")).join("");
|
|
23799
23999
|
var signVoiceOpsWebhookBody = async (input) => {
|
|
23800
24000
|
const encoder = new TextEncoder;
|
|
@@ -23924,7 +24124,7 @@ var verifyVoiceOpsWebhookSignature = async (input) => {
|
|
|
23924
24124
|
};
|
|
23925
24125
|
var createVoiceOpsWebhookReceiverRoutes = (options = {}) => {
|
|
23926
24126
|
const path = options.path ?? "/api/voice-ops/webhook";
|
|
23927
|
-
return new
|
|
24127
|
+
return new Elysia40().post(path, async ({ body, request, set }) => {
|
|
23928
24128
|
const bodyText = typeof body === "string" ? body : JSON.stringify(body);
|
|
23929
24129
|
if (options.signingSecret) {
|
|
23930
24130
|
const verification = await verifyVoiceOpsWebhookSignature({
|
|
@@ -24866,6 +25066,7 @@ export {
|
|
|
24866
25066
|
renderVoiceOpsStatusHTML,
|
|
24867
25067
|
renderVoiceOpsConsoleHTML,
|
|
24868
25068
|
renderVoiceOpsActionHistoryHTML,
|
|
25069
|
+
renderVoiceOperationsRecordHTML,
|
|
24869
25070
|
renderVoiceLiveLatencyHTML,
|
|
24870
25071
|
renderVoiceHandoffHealthHTML,
|
|
24871
25072
|
renderVoiceEvalHTML,
|
|
@@ -25054,6 +25255,7 @@ export {
|
|
|
25054
25255
|
createVoiceOpsRuntime,
|
|
25055
25256
|
createVoiceOpsConsoleRoutes,
|
|
25056
25257
|
createVoiceOpsActionAuditRoutes,
|
|
25258
|
+
createVoiceOperationsRecordRoutes,
|
|
25057
25259
|
createVoiceMemoryTraceSinkDeliveryStore,
|
|
25058
25260
|
createVoiceMemoryTraceEventStore,
|
|
25059
25261
|
createVoiceMemoryStore,
|
|
@@ -25178,6 +25380,7 @@ export {
|
|
|
25178
25380
|
buildVoiceOpsTaskFromReview,
|
|
25179
25381
|
buildVoiceOpsConsoleReport,
|
|
25180
25382
|
buildVoiceOpsActionHistoryReport,
|
|
25383
|
+
buildVoiceOperationsRecord,
|
|
25181
25384
|
buildVoiceDiagnosticsMarkdown,
|
|
25182
25385
|
buildVoiceDemoReadyReport,
|
|
25183
25386
|
buildVoiceDeliverySinkReport,
|