@absolutejs/voice 0.0.22-beta.221 → 0.0.22-beta.223
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 +8 -1
- package/dist/index.d.ts +2 -2
- package/dist/index.js +219 -9
- package/dist/observabilityExport.d.ts +77 -0
- package/package.json +1 -1
package/README.md
CHANGED
|
@@ -2756,6 +2756,13 @@ app.use(
|
|
|
2756
2756
|
artifactIntegrity: {
|
|
2757
2757
|
maxAgeMs: 15 * 60 * 1000
|
|
2758
2758
|
},
|
|
2759
|
+
deliveryDestinations: [
|
|
2760
|
+
{
|
|
2761
|
+
directory: '.voice-runtime/observability-exports',
|
|
2762
|
+
kind: 'file',
|
|
2763
|
+
label: 'Local customer-owned observability archive'
|
|
2764
|
+
}
|
|
2765
|
+
],
|
|
2759
2766
|
artifacts: [
|
|
2760
2767
|
{
|
|
2761
2768
|
id: 'latest-proof-pack',
|
|
@@ -2791,7 +2798,7 @@ const exportReport = await buildVoiceObservabilityExport({
|
|
|
2791
2798
|
});
|
|
2792
2799
|
```
|
|
2793
2800
|
|
|
2794
|
-
The route helper exposes JSON at `/api/voice/observability-export`, Markdown at `/voice/observability-export.md`, and HTML at `/voice/observability-export`. Path-backed artifacts are hashed with SHA-256 by default, include byte size and freshness metadata, and can fail the export when required evidence is missing or stale. Failed trace/audit deliveries fail the export report, pending deliveries warn, and every trace/audit envelope includes the linked operations-record URL when one is configured. This is the primitive to use when customers ask how voice evidence leaves the app without going through a hosted vendor dashboard.
|
|
2801
|
+
The route helper exposes JSON at `/api/voice/observability-export`, an artifact index at `/api/voice/observability-export/artifacts`, per-artifact downloads at `/api/voice/observability-export/artifacts/:artifactId`, delivery at `POST /api/voice/observability-export/deliveries`, Markdown at `/voice/observability-export.md`, and HTML at `/voice/observability-export`. Path-backed artifacts are hashed with SHA-256 by default, include byte size and freshness metadata, and can fail the export when required evidence is missing or stale. File delivery writes `manifest.json`, `artifact-index.json`, and artifact files into a customer-owned archive directory; webhook delivery posts the manifest and artifact index to a buyer-owned collector, SIEM bridge, or warehouse endpoint. Failed trace/audit deliveries fail the export report, pending deliveries warn, and every trace/audit envelope includes the linked operations-record URL when one is configured. This is the primitive to use when customers ask how voice evidence leaves the app without going through a hosted vendor dashboard.
|
|
2795
2802
|
|
|
2796
2803
|
Pass the same report into production readiness when export health should block deploys:
|
|
2797
2804
|
|
package/dist/index.d.ts
CHANGED
|
@@ -53,7 +53,7 @@ export { createVoiceReadinessProfile, recommendVoiceReadinessProfile } from './r
|
|
|
53
53
|
export { buildVoiceProviderContractMatrix, createVoiceProviderContractMatrixHTMLHandler, createVoiceProviderContractMatrixJSONHandler, createVoiceProviderContractMatrixPreset, createVoiceProviderContractMatrixRoutes, evaluateVoiceProviderStackGaps, renderVoiceProviderContractMatrixHTML, recommendVoiceProviderStack } from './providerStackRecommendations';
|
|
54
54
|
export { buildVoiceOpsConsoleReport, createVoiceOpsConsoleRoutes, renderVoiceOpsConsoleHTML } from './opsConsoleRoutes';
|
|
55
55
|
export { buildVoiceOperationsRecord, createVoiceOperationsRecordRoutes, renderVoiceOperationsRecordHTML, renderVoiceOperationsRecordIncidentMarkdown } from './operationsRecord';
|
|
56
|
-
export { buildVoiceObservabilityExport, createVoiceObservabilityExportRoutes, renderVoiceObservabilityExportMarkdown } from './observabilityExport';
|
|
56
|
+
export { buildVoiceObservabilityArtifactIndex, buildVoiceObservabilityExport, createVoiceObservabilityExportRoutes, deliverVoiceObservabilityExport, renderVoiceObservabilityExportMarkdown } from './observabilityExport';
|
|
57
57
|
export { buildVoiceOpsRecoveryReadinessCheck, buildVoiceOpsRecoveryReport, createVoiceOpsRecoveryRoutes, renderVoiceOpsRecoveryHTML, renderVoiceOpsRecoveryMarkdown } from './opsRecovery';
|
|
58
58
|
export { buildVoiceIncidentBundle, createStoredVoiceIncidentBundleArtifact, createVoiceIncidentBundleRoutes, createVoiceMemoryIncidentBundleStore, pruneVoiceIncidentBundleArtifacts, saveVoiceIncidentBundleArtifact } from './incidentBundle';
|
|
59
59
|
export { summarizeVoiceOpsStatus } from './opsStatus';
|
|
@@ -118,7 +118,7 @@ export type { VoiceProductionReadinessAction, VoiceProductionReadinessAuditOptio
|
|
|
118
118
|
export type { VoiceReadinessProfileName, VoiceReadinessProfileOptions, VoiceReadinessProfileRecommendation, VoiceReadinessProfileRecommendationScore, VoiceReadinessProfileRoutesOptions } from './readinessProfiles';
|
|
119
119
|
export type { VoiceProviderStackChoice, VoiceProviderStackCapabilities, VoiceProviderStackCapabilityGap, VoiceProviderStackCapabilityGapInput, VoiceProviderStackCapabilityGapReport, VoiceProviderContractCheck, VoiceProviderContractCheckStatus, VoiceProviderContractDefinition, VoiceProviderContractMatrixHandlerOptions, VoiceProviderContractMatrixHTMLHandlerOptions, VoiceProviderContractMatrixInput, VoiceProviderContractMatrixPresetOptions, VoiceProviderContractMatrixReport, VoiceProviderContractMatrixRoutesOptions, VoiceProviderContractMatrixRow, VoiceProviderStackInput, VoiceProviderStackKind, VoiceProviderStackRecommendation } from './providerStackRecommendations';
|
|
120
120
|
export type { VoiceOperationsRecord, VoiceOperationsRecordAgentHandoff, VoiceOperationsRecordAuditSummary, VoiceOperationsRecordIntegrationEventSummary, VoiceOperationsRecordOptions, VoiceOperationsRecordOutcome, VoiceOperationsRecordProviderDecision, VoiceOperationsRecordReviewSummary, VoiceOperationsRecordRoutesOptions, VoiceOperationsRecordStatus, VoiceOperationsRecordTaskSummary, VoiceOperationsRecordTranscriptTurn, VoiceOperationsRecordTool } from './operationsRecord';
|
|
121
|
-
export type { VoiceObservabilityExportArtifact, VoiceObservabilityExportArtifactKind, VoiceObservabilityExportDeliverySummary, VoiceObservabilityExportEnvelope, VoiceObservabilityExportIssue, VoiceObservabilityExportIssueCode, VoiceObservabilityExportOptions, VoiceObservabilityExportRedactionSummary, VoiceObservabilityExportReport, VoiceObservabilityExportRoutesOptions, VoiceObservabilityExportStatus } from './observabilityExport';
|
|
121
|
+
export type { VoiceObservabilityExportArtifact, VoiceObservabilityExportArtifactChecksum, VoiceObservabilityExportArtifactFreshness, VoiceObservabilityExportArtifactIndex, VoiceObservabilityExportArtifactIndexItem, VoiceObservabilityExportArtifactKind, VoiceObservabilityExportDeliverySummary, VoiceObservabilityExportDeliveryDestination, VoiceObservabilityExportDeliveryDestinationResult, VoiceObservabilityExportDeliveryOptions, VoiceObservabilityExportDeliveryReport, VoiceObservabilityExportEnvelope, VoiceObservabilityExportIssue, VoiceObservabilityExportIssueCode, VoiceObservabilityExportOptions, VoiceObservabilityExportRedactionSummary, VoiceObservabilityExportReport, VoiceObservabilityExportRoutesOptions, VoiceObservabilityExportStatus } from './observabilityExport';
|
|
122
122
|
export type { VoiceOpsRecoveryFailedSession, VoiceOpsRecoveryInterventionSummary, VoiceOpsRecoveryIssue, VoiceOpsRecoveryIssueCode, VoiceOpsRecoveryLinks, VoiceOpsRecoveryProviderSummary, VoiceOpsRecoveryReport, VoiceOpsRecoveryReportOptions, VoiceOpsRecoveryRoutesOptions, VoiceOpsRecoveryStatus } from './opsRecovery';
|
|
123
123
|
export type { StoredVoiceIncidentBundleArtifact, VoiceIncidentBundle, VoiceIncidentBundleArtifactOptions, VoiceIncidentBundleFormat, VoiceIncidentBundleOptions, VoiceIncidentBundleRetentionOptions, VoiceIncidentBundleRetentionReport, VoiceIncidentBundleRoutesOptions, VoiceIncidentBundleStore, VoiceIncidentBundleStoreFilter, VoiceIncidentBundleSummary } from './incidentBundle';
|
|
124
124
|
export type { VoiceQualityLink, VoiceQualityMetric, VoiceQualityReport, VoiceQualityRoutesOptions, VoiceQualityStatus, VoiceQualityThresholds } from './qualityRoutes';
|
package/dist/index.js
CHANGED
|
@@ -6972,14 +6972,14 @@ var createProofFetch = (provider, requests) => {
|
|
|
6972
6972
|
};
|
|
6973
6973
|
};
|
|
6974
6974
|
var createProofDialer = (input) => {
|
|
6975
|
-
const
|
|
6975
|
+
const fetch2 = createProofFetch(input.provider, input.requests);
|
|
6976
6976
|
if (input.provider === "twilio") {
|
|
6977
6977
|
return createVoiceTwilioCampaignDialer({
|
|
6978
6978
|
accountSid: "AC_dry_run",
|
|
6979
6979
|
answerUrl: joinUrlPath(input.baseUrl, "/api/twilio/voice"),
|
|
6980
6980
|
apiBaseUrl: "https://twilio.dry-run.absolutejs.local",
|
|
6981
6981
|
authToken: "dry-run-token",
|
|
6982
|
-
fetch,
|
|
6982
|
+
fetch: fetch2,
|
|
6983
6983
|
from: input.from,
|
|
6984
6984
|
statusCallbackEvents: ["answered", "completed"],
|
|
6985
6985
|
statusCallbackUrl: joinUrlPath(input.baseUrl, "/api/telephony-webhook")
|
|
@@ -6990,7 +6990,7 @@ var createProofDialer = (input) => {
|
|
|
6990
6990
|
apiBaseUrl: "https://telnyx.dry-run.absolutejs.local",
|
|
6991
6991
|
apiKey: "dry-run-token",
|
|
6992
6992
|
connectionId: "dry-run-connection",
|
|
6993
|
-
fetch,
|
|
6993
|
+
fetch: fetch2,
|
|
6994
6994
|
from: input.from,
|
|
6995
6995
|
webhookUrl: joinUrlPath(input.baseUrl, "/api/telnyx/webhook")
|
|
6996
6996
|
});
|
|
@@ -7001,7 +7001,7 @@ var createProofDialer = (input) => {
|
|
|
7001
7001
|
authId: "dry-run-auth-id",
|
|
7002
7002
|
authToken: "dry-run-token",
|
|
7003
7003
|
callbackUrl: joinUrlPath(input.baseUrl, "/api/plivo/webhook"),
|
|
7004
|
-
fetch,
|
|
7004
|
+
fetch: fetch2,
|
|
7005
7005
|
from: input.from
|
|
7006
7006
|
});
|
|
7007
7007
|
};
|
|
@@ -24943,7 +24943,8 @@ var createVoiceOperationsRecordRoutes = (options) => {
|
|
|
24943
24943
|
// src/observabilityExport.ts
|
|
24944
24944
|
import { Elysia as Elysia42 } from "elysia";
|
|
24945
24945
|
import { createHash } from "crypto";
|
|
24946
|
-
import { readFile as readFile2, stat } from "fs/promises";
|
|
24946
|
+
import { mkdir as mkdir4, readFile as readFile2, stat } from "fs/promises";
|
|
24947
|
+
import { join as join3 } from "path";
|
|
24947
24948
|
var isDeliveryStore = (value) => !Array.isArray(value) && typeof value.list === "function";
|
|
24948
24949
|
var getString18 = (value) => typeof value === "string" ? value : undefined;
|
|
24949
24950
|
var getProviderKind = (payload) => getString18(payload.kind) ?? getString18(payload.providerKind);
|
|
@@ -24982,6 +24983,41 @@ var checksumFile = async (path) => {
|
|
|
24982
24983
|
const buffer = await readFile2(path);
|
|
24983
24984
|
return createHash("sha256").update(buffer).digest("hex");
|
|
24984
24985
|
};
|
|
24986
|
+
var byteLength = (value) => new TextEncoder().encode(value).byteLength;
|
|
24987
|
+
var safeArtifactFileName = (artifact) => {
|
|
24988
|
+
const extension = artifact.contentType === "image/png" ? ".png" : artifact.contentType?.includes("markdown") ? ".md" : artifact.contentType?.includes("json") ? ".json" : "";
|
|
24989
|
+
return `${artifact.id.replace(/[^a-z0-9_.-]/gi, "-")}${extension}`;
|
|
24990
|
+
};
|
|
24991
|
+
var inferContentType = (artifact) => {
|
|
24992
|
+
if (artifact.contentType) {
|
|
24993
|
+
return artifact.contentType;
|
|
24994
|
+
}
|
|
24995
|
+
const path = artifact.path ? stripArtifactPathAnchor(artifact.path) : "";
|
|
24996
|
+
if (path.endsWith(".json")) {
|
|
24997
|
+
return "application/json; charset=utf-8";
|
|
24998
|
+
}
|
|
24999
|
+
if (path.endsWith(".md") || path.endsWith(".markdown")) {
|
|
25000
|
+
return "text/markdown; charset=utf-8";
|
|
25001
|
+
}
|
|
25002
|
+
if (path.endsWith(".html")) {
|
|
25003
|
+
return "text/html; charset=utf-8";
|
|
25004
|
+
}
|
|
25005
|
+
if (path.endsWith(".png")) {
|
|
25006
|
+
return "image/png";
|
|
25007
|
+
}
|
|
25008
|
+
if (path.endsWith(".jpg") || path.endsWith(".jpeg")) {
|
|
25009
|
+
return "image/jpeg";
|
|
25010
|
+
}
|
|
25011
|
+
if (path.endsWith(".txt") || path.endsWith(".log")) {
|
|
25012
|
+
return "text/plain; charset=utf-8";
|
|
25013
|
+
}
|
|
25014
|
+
return "application/octet-stream";
|
|
25015
|
+
};
|
|
25016
|
+
var addArtifactDownloadHrefs = (artifacts, links) => artifacts.map((artifact) => ({
|
|
25017
|
+
...artifact,
|
|
25018
|
+
contentType: artifact.contentType ?? inferContentType(artifact),
|
|
25019
|
+
downloadHref: artifact.downloadHref ?? (artifact.path ? links?.artifactDownload?.(artifact) : undefined)
|
|
25020
|
+
}));
|
|
24985
25021
|
var verifyArtifact = async (artifact, options) => {
|
|
24986
25022
|
const now = options.now ?? Date.now();
|
|
24987
25023
|
const maxAgeMs = artifact.maxAgeMs ?? options.maxAgeMs;
|
|
@@ -25171,7 +25207,7 @@ var buildVoiceObservabilityExport = async (options = {}) => {
|
|
|
25171
25207
|
const traceDeliverySummary = traceDeliveries ? await summarizeVoiceTraceSinkDeliveries(traceDeliveries) : undefined;
|
|
25172
25208
|
const auditDeliverySummary = auditDeliveries ? await summarizeVoiceAuditSinkDeliveries(auditDeliveries) : undefined;
|
|
25173
25209
|
const operationArtifacts = operationsRecords.map((record) => createOperationArtifact(record, options.links?.operationsRecord?.(record.sessionId)));
|
|
25174
|
-
const artifacts = await verifyArtifacts([...operationArtifacts, ...options.artifacts ?? []], options.artifactIntegrity);
|
|
25210
|
+
const artifacts = addArtifactDownloadHrefs(await verifyArtifacts([...operationArtifacts, ...options.artifacts ?? []], options.artifactIntegrity), options.links);
|
|
25175
25211
|
const operationHrefBySessionId = new Map(sessionIds.map((sessionId) => [
|
|
25176
25212
|
sessionId,
|
|
25177
25213
|
options.links?.operationsRecord?.(sessionId)
|
|
@@ -25252,19 +25288,191 @@ ${artifacts}
|
|
|
25252
25288
|
${issues}
|
|
25253
25289
|
`;
|
|
25254
25290
|
};
|
|
25291
|
+
var buildVoiceObservabilityArtifactIndex = (report) => {
|
|
25292
|
+
const artifacts = report.artifacts.map((artifact) => ({
|
|
25293
|
+
bytes: artifact.bytes,
|
|
25294
|
+
checksum: artifact.checksum,
|
|
25295
|
+
contentType: artifact.contentType,
|
|
25296
|
+
downloadHref: artifact.downloadHref,
|
|
25297
|
+
freshness: artifact.freshness,
|
|
25298
|
+
href: artifact.href,
|
|
25299
|
+
id: artifact.id,
|
|
25300
|
+
kind: artifact.kind,
|
|
25301
|
+
label: artifact.label,
|
|
25302
|
+
required: artifact.required,
|
|
25303
|
+
sessionId: artifact.sessionId,
|
|
25304
|
+
status: artifact.status
|
|
25305
|
+
}));
|
|
25306
|
+
return {
|
|
25307
|
+
artifacts,
|
|
25308
|
+
checkedAt: report.checkedAt,
|
|
25309
|
+
status: report.status,
|
|
25310
|
+
summary: {
|
|
25311
|
+
downloadable: artifacts.filter((artifact) => artifact.downloadHref).length,
|
|
25312
|
+
failed: artifacts.filter((artifact) => artifact.status === "fail").length,
|
|
25313
|
+
required: artifacts.filter((artifact) => artifact.required).length,
|
|
25314
|
+
total: artifacts.length,
|
|
25315
|
+
warn: artifacts.filter((artifact) => artifact.status === "warn").length
|
|
25316
|
+
}
|
|
25317
|
+
};
|
|
25318
|
+
};
|
|
25319
|
+
var deliverVoiceObservabilityExport = async (options) => {
|
|
25320
|
+
const checkedAt = Date.now();
|
|
25321
|
+
const runId = options.runId ?? new Date(checkedAt).toISOString().replaceAll(":", "-");
|
|
25322
|
+
const artifactIndex = buildVoiceObservabilityArtifactIndex(options.report);
|
|
25323
|
+
const manifest = `${JSON.stringify(options.report, null, 2)}
|
|
25324
|
+
`;
|
|
25325
|
+
const index = `${JSON.stringify(artifactIndex, null, 2)}
|
|
25326
|
+
`;
|
|
25327
|
+
const destinations = await Promise.all(options.destinations.map(async (destination) => {
|
|
25328
|
+
const destinationId = destination.id ?? `${destination.kind}-${destination.label ?? "export"}`;
|
|
25329
|
+
const label = destination.label ?? (destination.kind === "file" ? "File observability export" : "Webhook observability export");
|
|
25330
|
+
try {
|
|
25331
|
+
if (destination.kind === "file") {
|
|
25332
|
+
const target = join3(destination.directory, runId);
|
|
25333
|
+
await mkdir4(join3(target, "artifacts"), { recursive: true });
|
|
25334
|
+
await Bun.write(join3(target, "manifest.json"), manifest);
|
|
25335
|
+
await Bun.write(join3(target, "artifact-index.json"), index);
|
|
25336
|
+
if (destination.includeArtifacts !== false) {
|
|
25337
|
+
for (const artifact of options.report.artifacts) {
|
|
25338
|
+
if (!artifact.path) {
|
|
25339
|
+
continue;
|
|
25340
|
+
}
|
|
25341
|
+
await Bun.write(join3(target, "artifacts", safeArtifactFileName(artifact)), await readFile2(stripArtifactPathAnchor(artifact.path)));
|
|
25342
|
+
}
|
|
25343
|
+
}
|
|
25344
|
+
return {
|
|
25345
|
+
artifactCount: destination.includeArtifacts === false ? 0 : options.report.artifacts.filter((artifact) => artifact.path).length,
|
|
25346
|
+
deliveredAt: Date.now(),
|
|
25347
|
+
destinationId,
|
|
25348
|
+
destinationKind: destination.kind,
|
|
25349
|
+
label,
|
|
25350
|
+
manifestBytes: byteLength(manifest),
|
|
25351
|
+
status: "delivered",
|
|
25352
|
+
target
|
|
25353
|
+
};
|
|
25354
|
+
}
|
|
25355
|
+
const controller = new AbortController;
|
|
25356
|
+
const timeout = setTimeout(() => controller.abort(), destination.timeoutMs ?? 1e4);
|
|
25357
|
+
try {
|
|
25358
|
+
const response = await (destination.fetch ?? fetch)(destination.url, {
|
|
25359
|
+
body: JSON.stringify({
|
|
25360
|
+
artifactIndex,
|
|
25361
|
+
artifacts: destination.includeArtifacts === false ? [] : options.report.artifacts,
|
|
25362
|
+
manifest: options.report,
|
|
25363
|
+
runId,
|
|
25364
|
+
source: "absolutejs-voice"
|
|
25365
|
+
}),
|
|
25366
|
+
headers: {
|
|
25367
|
+
"content-type": "application/json",
|
|
25368
|
+
...destination.headers ?? {}
|
|
25369
|
+
},
|
|
25370
|
+
method: "POST",
|
|
25371
|
+
signal: controller.signal
|
|
25372
|
+
});
|
|
25373
|
+
if (!response.ok) {
|
|
25374
|
+
throw new Error(`Webhook returned HTTP ${response.status}`);
|
|
25375
|
+
}
|
|
25376
|
+
} finally {
|
|
25377
|
+
clearTimeout(timeout);
|
|
25378
|
+
}
|
|
25379
|
+
return {
|
|
25380
|
+
artifactCount: destination.includeArtifacts === false ? 0 : options.report.artifacts.length,
|
|
25381
|
+
deliveredAt: Date.now(),
|
|
25382
|
+
destinationId,
|
|
25383
|
+
destinationKind: destination.kind,
|
|
25384
|
+
label,
|
|
25385
|
+
manifestBytes: byteLength(manifest),
|
|
25386
|
+
status: "delivered",
|
|
25387
|
+
target: destination.url
|
|
25388
|
+
};
|
|
25389
|
+
} catch (error) {
|
|
25390
|
+
return {
|
|
25391
|
+
artifactCount: 0,
|
|
25392
|
+
deliveredAt: Date.now(),
|
|
25393
|
+
destinationId,
|
|
25394
|
+
destinationKind: destination.kind,
|
|
25395
|
+
error: error instanceof Error ? error.message : String(error),
|
|
25396
|
+
label,
|
|
25397
|
+
manifestBytes: byteLength(manifest),
|
|
25398
|
+
status: "failed",
|
|
25399
|
+
target: destination.kind === "file" ? destination.directory : destination.url
|
|
25400
|
+
};
|
|
25401
|
+
}
|
|
25402
|
+
}));
|
|
25403
|
+
const failed = destinations.filter((destination) => destination.status === "failed").length;
|
|
25404
|
+
return {
|
|
25405
|
+
checkedAt,
|
|
25406
|
+
destinations,
|
|
25407
|
+
exportStatus: options.report.status,
|
|
25408
|
+
status: failed > 0 || options.report.status === "fail" ? "fail" : options.report.status === "warn" ? "warn" : "pass",
|
|
25409
|
+
summary: {
|
|
25410
|
+
delivered: destinations.length - failed,
|
|
25411
|
+
failed,
|
|
25412
|
+
total: destinations.length
|
|
25413
|
+
}
|
|
25414
|
+
};
|
|
25415
|
+
};
|
|
25255
25416
|
var createVoiceObservabilityExportRoutes = (options = {}) => {
|
|
25256
25417
|
const path = options.path ?? "/api/voice/observability-export";
|
|
25418
|
+
const artifactIndexPath = options.artifactIndexPath ?? `${path}/artifacts`;
|
|
25419
|
+
const artifactDownloadPath = options.artifactDownloadPath ?? `${path}/artifacts`;
|
|
25420
|
+
const deliveryPath = options.deliveryPath ?? `${path}/deliveries`;
|
|
25257
25421
|
const markdownPath = options.markdownPath ?? "/voice/observability-export.md";
|
|
25258
25422
|
const htmlPath = options.htmlPath ?? "/voice/observability-export";
|
|
25259
25423
|
const headers = {
|
|
25260
25424
|
"cache-control": "no-store",
|
|
25261
25425
|
...options.headers ?? {}
|
|
25262
25426
|
};
|
|
25263
|
-
const buildReport = () => buildVoiceObservabilityExport(
|
|
25427
|
+
const buildReport = () => buildVoiceObservabilityExport({
|
|
25428
|
+
...options,
|
|
25429
|
+
links: {
|
|
25430
|
+
...options.links,
|
|
25431
|
+
artifactDownload: options.links?.artifactDownload ?? (artifactDownloadPath ? (artifact) => `${artifactDownloadPath}/${encodeURIComponent(artifact.id)}` : undefined)
|
|
25432
|
+
}
|
|
25433
|
+
});
|
|
25264
25434
|
const app = new Elysia42({
|
|
25265
25435
|
name: options.name ?? "absolute-voice-observability-export"
|
|
25266
25436
|
});
|
|
25267
25437
|
app.get(path, async () => Response.json(await buildReport(), { headers }));
|
|
25438
|
+
if (artifactIndexPath !== false) {
|
|
25439
|
+
app.get(artifactIndexPath, async () => Response.json(buildVoiceObservabilityArtifactIndex(await buildReport()), { headers }));
|
|
25440
|
+
}
|
|
25441
|
+
if (artifactDownloadPath !== false) {
|
|
25442
|
+
app.get(`${artifactDownloadPath}/:artifactId`, async ({ params }) => {
|
|
25443
|
+
const artifactId = decodeURIComponent(params.artifactId);
|
|
25444
|
+
const report = await buildReport();
|
|
25445
|
+
const artifact = report.artifacts.find((item) => item.id === artifactId);
|
|
25446
|
+
if (!artifact?.path) {
|
|
25447
|
+
return Response.json({ error: "Artifact is not downloadable.", artifactId }, { headers, status: 404 });
|
|
25448
|
+
}
|
|
25449
|
+
try {
|
|
25450
|
+
const body = await readFile2(stripArtifactPathAnchor(artifact.path));
|
|
25451
|
+
return new Response(body, {
|
|
25452
|
+
headers: {
|
|
25453
|
+
...headers,
|
|
25454
|
+
"content-disposition": `attachment; filename="${encodeURIComponent(artifact.id)}"`,
|
|
25455
|
+
"content-type": artifact.contentType ?? inferContentType(artifact),
|
|
25456
|
+
...artifact.checksum ? {
|
|
25457
|
+
"x-absolute-voice-artifact-sha256": artifact.checksum.value
|
|
25458
|
+
} : {},
|
|
25459
|
+
"x-absolute-voice-artifact-id": artifact.id,
|
|
25460
|
+
...artifact.freshness ? {
|
|
25461
|
+
"x-absolute-voice-artifact-freshness": artifact.freshness.status
|
|
25462
|
+
} : {}
|
|
25463
|
+
}
|
|
25464
|
+
});
|
|
25465
|
+
} catch {
|
|
25466
|
+
return Response.json({ error: "Artifact file is not available.", artifactId }, { headers, status: 404 });
|
|
25467
|
+
}
|
|
25468
|
+
});
|
|
25469
|
+
}
|
|
25470
|
+
if (deliveryPath !== false && options.deliveryDestinations) {
|
|
25471
|
+
app.post(deliveryPath, async () => Response.json(await deliverVoiceObservabilityExport({
|
|
25472
|
+
destinations: options.deliveryDestinations ?? [],
|
|
25473
|
+
report: await buildReport()
|
|
25474
|
+
}), { headers }));
|
|
25475
|
+
}
|
|
25268
25476
|
if (markdownPath !== false) {
|
|
25269
25477
|
app.get(markdownPath, async () => {
|
|
25270
25478
|
const report = await buildReport();
|
|
@@ -27059,7 +27267,7 @@ var createVoiceOpsRuntime = (config) => {
|
|
|
27059
27267
|
pollIntervalMs: _pollIntervalMs,
|
|
27060
27268
|
backoffMs,
|
|
27061
27269
|
eventTypes,
|
|
27062
|
-
fetch,
|
|
27270
|
+
fetch: fetch2,
|
|
27063
27271
|
headers,
|
|
27064
27272
|
retries,
|
|
27065
27273
|
signingSecret,
|
|
@@ -27073,7 +27281,7 @@ var createVoiceOpsRuntime = (config) => {
|
|
|
27073
27281
|
webhook: {
|
|
27074
27282
|
backoffMs,
|
|
27075
27283
|
eventTypes,
|
|
27076
|
-
fetch,
|
|
27284
|
+
fetch: fetch2,
|
|
27077
27285
|
headers,
|
|
27078
27286
|
retries,
|
|
27079
27287
|
signingSecret,
|
|
@@ -28015,6 +28223,7 @@ export {
|
|
|
28015
28223
|
evaluateVoiceProviderStackGaps,
|
|
28016
28224
|
encodeTwilioMulawBase64,
|
|
28017
28225
|
deliverVoiceTraceEventsToSinks,
|
|
28226
|
+
deliverVoiceObservabilityExport,
|
|
28018
28227
|
deliverVoiceIntegrationEventToSinks,
|
|
28019
28228
|
deliverVoiceIntegrationEvent,
|
|
28020
28229
|
deliverVoiceHandoffDelivery,
|
|
@@ -28291,6 +28500,7 @@ export {
|
|
|
28291
28500
|
buildVoiceOpsActionHistoryReport,
|
|
28292
28501
|
buildVoiceOperationsRecord,
|
|
28293
28502
|
buildVoiceObservabilityExport,
|
|
28503
|
+
buildVoiceObservabilityArtifactIndex,
|
|
28294
28504
|
buildVoiceLiveOpsControlState,
|
|
28295
28505
|
buildVoiceLatencySLOGate,
|
|
28296
28506
|
buildVoiceIncidentBundle,
|
|
@@ -21,6 +21,7 @@ export type VoiceObservabilityExportArtifact = {
|
|
|
21
21
|
bytes?: number;
|
|
22
22
|
checksum?: VoiceObservabilityExportArtifactChecksum;
|
|
23
23
|
contentType?: string;
|
|
24
|
+
downloadHref?: string;
|
|
24
25
|
freshness?: VoiceObservabilityExportArtifactFreshness;
|
|
25
26
|
generatedAt?: number | string;
|
|
26
27
|
href?: string;
|
|
@@ -80,6 +81,75 @@ export type VoiceObservabilityExportReport = {
|
|
|
80
81
|
traceEvents: number;
|
|
81
82
|
};
|
|
82
83
|
};
|
|
84
|
+
export type VoiceObservabilityExportArtifactIndexItem = {
|
|
85
|
+
bytes?: number;
|
|
86
|
+
checksum?: VoiceObservabilityExportArtifactChecksum;
|
|
87
|
+
contentType?: string;
|
|
88
|
+
downloadHref?: string;
|
|
89
|
+
freshness?: VoiceObservabilityExportArtifactFreshness;
|
|
90
|
+
href?: string;
|
|
91
|
+
id: string;
|
|
92
|
+
kind: VoiceObservabilityExportArtifactKind;
|
|
93
|
+
label: string;
|
|
94
|
+
required?: boolean;
|
|
95
|
+
sessionId?: string;
|
|
96
|
+
status?: VoiceObservabilityExportStatus;
|
|
97
|
+
};
|
|
98
|
+
export type VoiceObservabilityExportArtifactIndex = {
|
|
99
|
+
artifacts: VoiceObservabilityExportArtifactIndexItem[];
|
|
100
|
+
checkedAt: number;
|
|
101
|
+
status: VoiceObservabilityExportStatus;
|
|
102
|
+
summary: {
|
|
103
|
+
downloadable: number;
|
|
104
|
+
failed: number;
|
|
105
|
+
required: number;
|
|
106
|
+
total: number;
|
|
107
|
+
warn: number;
|
|
108
|
+
};
|
|
109
|
+
};
|
|
110
|
+
export type VoiceObservabilityExportDeliveryDestination = {
|
|
111
|
+
directory: string;
|
|
112
|
+
id?: string;
|
|
113
|
+
includeArtifacts?: boolean;
|
|
114
|
+
kind: 'file';
|
|
115
|
+
label?: string;
|
|
116
|
+
} | {
|
|
117
|
+
fetch?: typeof fetch;
|
|
118
|
+
headers?: Record<string, string>;
|
|
119
|
+
id?: string;
|
|
120
|
+
includeArtifacts?: boolean;
|
|
121
|
+
kind: 'webhook';
|
|
122
|
+
label?: string;
|
|
123
|
+
timeoutMs?: number;
|
|
124
|
+
url: string;
|
|
125
|
+
};
|
|
126
|
+
export type VoiceObservabilityExportDeliveryDestinationResult = {
|
|
127
|
+
artifactCount: number;
|
|
128
|
+
deliveredAt: number;
|
|
129
|
+
destinationId: string;
|
|
130
|
+
destinationKind: VoiceObservabilityExportDeliveryDestination['kind'];
|
|
131
|
+
error?: string;
|
|
132
|
+
label: string;
|
|
133
|
+
manifestBytes: number;
|
|
134
|
+
status: 'delivered' | 'failed';
|
|
135
|
+
target: string;
|
|
136
|
+
};
|
|
137
|
+
export type VoiceObservabilityExportDeliveryReport = {
|
|
138
|
+
checkedAt: number;
|
|
139
|
+
destinations: VoiceObservabilityExportDeliveryDestinationResult[];
|
|
140
|
+
exportStatus: VoiceObservabilityExportStatus;
|
|
141
|
+
status: VoiceObservabilityExportStatus;
|
|
142
|
+
summary: {
|
|
143
|
+
delivered: number;
|
|
144
|
+
failed: number;
|
|
145
|
+
total: number;
|
|
146
|
+
};
|
|
147
|
+
};
|
|
148
|
+
export type VoiceObservabilityExportDeliveryOptions = {
|
|
149
|
+
destinations: VoiceObservabilityExportDeliveryDestination[];
|
|
150
|
+
report: VoiceObservabilityExportReport;
|
|
151
|
+
runId?: string;
|
|
152
|
+
};
|
|
83
153
|
export type VoiceObservabilityExportOptions = {
|
|
84
154
|
artifacts?: VoiceObservabilityExportArtifact[];
|
|
85
155
|
artifactIntegrity?: {
|
|
@@ -94,6 +164,7 @@ export type VoiceObservabilityExportOptions = {
|
|
|
94
164
|
events?: StoredVoiceTraceEvent[];
|
|
95
165
|
includeOperationsRecords?: boolean;
|
|
96
166
|
links?: {
|
|
167
|
+
artifactDownload?: (artifact: VoiceObservabilityExportArtifact) => string;
|
|
97
168
|
operationsRecord?: (sessionId: string) => string;
|
|
98
169
|
};
|
|
99
170
|
operationsRecords?: VoiceOperationsRecord[];
|
|
@@ -104,6 +175,10 @@ export type VoiceObservabilityExportOptions = {
|
|
|
104
175
|
};
|
|
105
176
|
export type VoiceObservabilityExportRoutesOptions = VoiceObservabilityExportOptions & {
|
|
106
177
|
headers?: HeadersInit;
|
|
178
|
+
artifactDownloadPath?: false | string;
|
|
179
|
+
artifactIndexPath?: false | string;
|
|
180
|
+
deliveryDestinations?: VoiceObservabilityExportDeliveryDestination[];
|
|
181
|
+
deliveryPath?: false | string;
|
|
107
182
|
htmlPath?: false | string;
|
|
108
183
|
markdownPath?: false | string;
|
|
109
184
|
name?: string;
|
|
@@ -115,6 +190,8 @@ export declare const buildVoiceObservabilityExport: (options?: VoiceObservabilit
|
|
|
115
190
|
export declare const renderVoiceObservabilityExportMarkdown: (report: VoiceObservabilityExportReport, options?: {
|
|
116
191
|
title?: string;
|
|
117
192
|
}) => string;
|
|
193
|
+
export declare const buildVoiceObservabilityArtifactIndex: (report: VoiceObservabilityExportReport) => VoiceObservabilityExportArtifactIndex;
|
|
194
|
+
export declare const deliverVoiceObservabilityExport: (options: VoiceObservabilityExportDeliveryOptions) => Promise<VoiceObservabilityExportDeliveryReport>;
|
|
118
195
|
export declare const createVoiceObservabilityExportRoutes: (options?: VoiceObservabilityExportRoutesOptions) => Elysia<"", {
|
|
119
196
|
decorator: {};
|
|
120
197
|
store: {};
|