@absolutejs/voice 0.0.22-beta.322 → 0.0.22-beta.324

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.
@@ -20,6 +20,7 @@ import type { VoiceCampaignReadinessProofReport } from './campaign';
20
20
  import { type VoiceOpsRecoveryReport } from './opsRecovery';
21
21
  import { type VoiceObservabilityExportDeliveryHistory, type VoiceObservabilityExportDeliveryReceiptStore, type VoiceObservabilityExportReplayReport, type VoiceObservabilityExportReplaySource, type VoiceObservabilityExportReport } from './observabilityExport';
22
22
  import type { VoiceMediaPipelineReport } from './mediaPipelineRoutes';
23
+ import type { VoiceTelephonyMediaReport } from './telephonyMediaRoutes';
23
24
  import type { MediaWebRTCStatsReport } from '@absolutejs/media';
24
25
  export type VoiceProductionReadinessObservabilityExportDeliveryHistoryOptions = {
25
26
  failOnMissing?: boolean;
@@ -140,6 +141,7 @@ export type VoiceProductionReadinessReport = {
140
141
  opsRecovery?: string;
141
142
  phoneAgentSmoke?: string;
142
143
  telephonyWebhookSecurity?: string;
144
+ telephonyMedia?: string;
143
145
  providerContracts?: string;
144
146
  providerOrchestration?: string;
145
147
  providerRoutingContracts?: string;
@@ -294,6 +296,14 @@ export type VoiceProductionReadinessReport = {
294
296
  status: VoiceProductionReadinessStatus;
295
297
  warned: number;
296
298
  };
299
+ telephonyMedia?: {
300
+ audioBytes: number;
301
+ carriers: number;
302
+ failed: number;
303
+ issues: number;
304
+ passed: number;
305
+ status: VoiceProductionReadinessStatus;
306
+ };
297
307
  providerRoutingContracts?: {
298
308
  failed: number;
299
309
  passed: number;
@@ -481,6 +491,10 @@ export type VoiceProductionReadinessRoutesOptions = {
481
491
  query: Record<string, unknown>;
482
492
  request: Request;
483
493
  }) => Promise<MediaWebRTCStatsReport> | MediaWebRTCStatsReport);
494
+ telephonyMedia?: false | VoiceTelephonyMediaReport | ((input: {
495
+ query: Record<string, unknown>;
496
+ request: Request;
497
+ }) => Promise<VoiceTelephonyMediaReport> | VoiceTelephonyMediaReport);
484
498
  opsActionHistory?: false | VoiceProductionReadinessOpsActionHistoryOptions;
485
499
  opsRecovery?: false | VoiceOpsRecoveryReport | ((input: {
486
500
  query: Record<string, unknown>;
@@ -5050,6 +5050,63 @@ var stringStat = (stat, key) => {
5050
5050
  };
5051
5051
  var statKey = (stat) => String(stat.id ?? stringStat(stat, "ssrc") ?? numericStat(stat, "ssrc") ?? stringStat(stat, "trackIdentifier") ?? stringStat(stat, "mid") ?? "unknown");
5052
5052
  var secondsToMs = (value) => value === undefined ? undefined : value * 1000;
5053
+ var DEFAULT_TELEPHONY_FORMAT = {
5054
+ channels: 1,
5055
+ container: "raw",
5056
+ encoding: "mulaw",
5057
+ sampleRateHz: 8000
5058
+ };
5059
+ var bytesToBase64 = (audio) => {
5060
+ const bytes = audio instanceof ArrayBuffer ? new Uint8Array(audio) : new Uint8Array(audio.buffer, audio.byteOffset, audio.byteLength);
5061
+ return Buffer.from(bytes).toString("base64");
5062
+ };
5063
+ var base64ToBytes = (value) => new Uint8Array(Buffer.from(value, "base64"));
5064
+ var unknownRecord = (value) => value && typeof value === "object" ? value : {};
5065
+ var firstString = (records, keys) => {
5066
+ for (const record of records) {
5067
+ for (const key of keys) {
5068
+ const value = record[key];
5069
+ if (typeof value === "string" && value.length > 0) {
5070
+ return value;
5071
+ }
5072
+ if (typeof value === "number" && Number.isFinite(value)) {
5073
+ return String(value);
5074
+ }
5075
+ }
5076
+ }
5077
+ return;
5078
+ };
5079
+ var firstNumber = (records, keys) => {
5080
+ for (const record of records) {
5081
+ for (const key of keys) {
5082
+ const value = record[key];
5083
+ if (typeof value === "number" && Number.isFinite(value)) {
5084
+ return value;
5085
+ }
5086
+ if (typeof value === "string") {
5087
+ const parsed = Number(value);
5088
+ if (Number.isFinite(parsed)) {
5089
+ return parsed;
5090
+ }
5091
+ }
5092
+ }
5093
+ }
5094
+ return;
5095
+ };
5096
+ var telephonyDirection = (track) => {
5097
+ const normalized = track?.toLowerCase();
5098
+ if (!normalized) {
5099
+ return "unknown";
5100
+ }
5101
+ if (normalized.includes("inbound") || normalized.includes("caller") || normalized.includes("in")) {
5102
+ return "inbound";
5103
+ }
5104
+ if (normalized.includes("outbound") || normalized.includes("assistant") || normalized.includes("out")) {
5105
+ return "outbound";
5106
+ }
5107
+ return "unknown";
5108
+ };
5109
+ var telephonyFrameKind = (direction) => direction === "outbound" ? "assistant-audio" : "input-audio";
5053
5110
  var normalizeWebRTCStat = (stat) => {
5054
5111
  const sample = {};
5055
5112
  for (const [key, value] of Object.entries(stat)) {
@@ -5059,6 +5116,113 @@ var normalizeWebRTCStat = (stat) => {
5059
5116
  }
5060
5117
  return sample;
5061
5118
  };
5119
+ var parseTelephonyMediaFrame = (input) => {
5120
+ const envelope = input.envelope;
5121
+ const media = unknownRecord(envelope.media);
5122
+ const payload = firstString([media, envelope], ["payload", "audio", "data"]) ?? firstString([unknownRecord(envelope.message)], ["payload"]);
5123
+ if (!payload) {
5124
+ return;
5125
+ }
5126
+ const carrier = input.carrier ?? firstString([envelope], ["provider"]) ?? "telephony";
5127
+ const streamId = firstString([media, envelope], ["streamSid", "stream_id", "streamId", "streamId", "callSid", "call_id"]);
5128
+ const sequenceNumber = firstString([media, envelope], ["sequenceNumber", "sequence_number", "chunk"]);
5129
+ const track = firstString([media, envelope], ["track", "direction"]);
5130
+ const direction = telephonyDirection(track);
5131
+ const timestamp = firstNumber([media, envelope], ["timestamp", "time", "startedAt"]);
5132
+ return {
5133
+ at: timestamp,
5134
+ audio: base64ToBytes(payload),
5135
+ format: input.format ?? DEFAULT_TELEPHONY_FORMAT,
5136
+ id: [
5137
+ carrier,
5138
+ streamId ?? input.sessionId ?? "stream",
5139
+ sequenceNumber ?? timestamp ?? Date.now()
5140
+ ].join(":"),
5141
+ kind: telephonyFrameKind(direction),
5142
+ metadata: {
5143
+ carrier,
5144
+ direction,
5145
+ event: firstString([envelope], ["event", "type"]),
5146
+ sequenceNumber,
5147
+ streamId,
5148
+ track
5149
+ },
5150
+ sessionId: input.sessionId ?? streamId,
5151
+ source: "telephony"
5152
+ };
5153
+ };
5154
+ var serializeTelephonyMediaFrame = (input) => {
5155
+ const carrier = input.carrier ?? input.frame.metadata?.carrier ?? "telephony";
5156
+ const streamId = input.streamId ?? (typeof input.frame.metadata?.streamId === "string" ? input.frame.metadata.streamId : input.frame.sessionId);
5157
+ const sequenceNumber = input.sequenceNumber ?? (typeof input.frame.metadata?.sequenceNumber === "string" || typeof input.frame.metadata?.sequenceNumber === "number" ? input.frame.metadata.sequenceNumber : undefined);
5158
+ const direction = input.frame.kind === "assistant-audio" ? "outbound" : "inbound";
5159
+ const payload = input.frame.audio ? bytesToBase64(input.frame.audio) : "";
5160
+ if (carrier === "twilio") {
5161
+ return {
5162
+ event: "media",
5163
+ sequenceNumber,
5164
+ streamSid: streamId,
5165
+ media: {
5166
+ payload,
5167
+ timestamp: input.frame.at,
5168
+ track: direction
5169
+ }
5170
+ };
5171
+ }
5172
+ if (carrier === "telnyx") {
5173
+ return {
5174
+ event: "media",
5175
+ stream_id: streamId,
5176
+ sequence_number: sequenceNumber,
5177
+ media: {
5178
+ payload,
5179
+ timestamp: input.frame.at,
5180
+ track: direction
5181
+ }
5182
+ };
5183
+ }
5184
+ if (carrier === "plivo") {
5185
+ return {
5186
+ event: "media",
5187
+ streamId,
5188
+ sequenceNumber,
5189
+ media: {
5190
+ payload,
5191
+ timestamp: input.frame.at,
5192
+ track: direction
5193
+ }
5194
+ };
5195
+ }
5196
+ return {
5197
+ event: "media",
5198
+ provider: carrier,
5199
+ sequenceNumber,
5200
+ streamId,
5201
+ media: {
5202
+ payload,
5203
+ timestamp: input.frame.at,
5204
+ track: direction
5205
+ }
5206
+ };
5207
+ };
5208
+ var createTelephonyMediaSerializer = (input) => {
5209
+ const format = input.format ?? DEFAULT_TELEPHONY_FORMAT;
5210
+ return {
5211
+ carrier: input.carrier,
5212
+ format,
5213
+ parse: (envelope) => parseTelephonyMediaFrame({
5214
+ carrier: input.carrier,
5215
+ envelope,
5216
+ format,
5217
+ sessionId: input.sessionId ?? input.streamId
5218
+ }),
5219
+ serialize: (frame) => serializeTelephonyMediaFrame({
5220
+ carrier: input.carrier,
5221
+ frame,
5222
+ streamId: input.streamId
5223
+ })
5224
+ };
5225
+ };
5062
5226
  var buildMediaResamplingPlan = (input) => {
5063
5227
  const required = !formatMatches(input.inputFormat, input.outputFormat);
5064
5228
  return {
@@ -3384,6 +3384,63 @@ var stringStat = (stat, key) => {
3384
3384
  };
3385
3385
  var statKey = (stat) => String(stat.id ?? stringStat(stat, "ssrc") ?? numericStat(stat, "ssrc") ?? stringStat(stat, "trackIdentifier") ?? stringStat(stat, "mid") ?? "unknown");
3386
3386
  var secondsToMs = (value) => value === undefined ? undefined : value * 1000;
3387
+ var DEFAULT_TELEPHONY_FORMAT = {
3388
+ channels: 1,
3389
+ container: "raw",
3390
+ encoding: "mulaw",
3391
+ sampleRateHz: 8000
3392
+ };
3393
+ var bytesToBase64 = (audio) => {
3394
+ const bytes = audio instanceof ArrayBuffer ? new Uint8Array(audio) : new Uint8Array(audio.buffer, audio.byteOffset, audio.byteLength);
3395
+ return Buffer.from(bytes).toString("base64");
3396
+ };
3397
+ var base64ToBytes = (value) => new Uint8Array(Buffer.from(value, "base64"));
3398
+ var unknownRecord = (value) => value && typeof value === "object" ? value : {};
3399
+ var firstString = (records, keys) => {
3400
+ for (const record of records) {
3401
+ for (const key of keys) {
3402
+ const value = record[key];
3403
+ if (typeof value === "string" && value.length > 0) {
3404
+ return value;
3405
+ }
3406
+ if (typeof value === "number" && Number.isFinite(value)) {
3407
+ return String(value);
3408
+ }
3409
+ }
3410
+ }
3411
+ return;
3412
+ };
3413
+ var firstNumber = (records, keys) => {
3414
+ for (const record of records) {
3415
+ for (const key of keys) {
3416
+ const value = record[key];
3417
+ if (typeof value === "number" && Number.isFinite(value)) {
3418
+ return value;
3419
+ }
3420
+ if (typeof value === "string") {
3421
+ const parsed = Number(value);
3422
+ if (Number.isFinite(parsed)) {
3423
+ return parsed;
3424
+ }
3425
+ }
3426
+ }
3427
+ }
3428
+ return;
3429
+ };
3430
+ var telephonyDirection = (track) => {
3431
+ const normalized = track?.toLowerCase();
3432
+ if (!normalized) {
3433
+ return "unknown";
3434
+ }
3435
+ if (normalized.includes("inbound") || normalized.includes("caller") || normalized.includes("in")) {
3436
+ return "inbound";
3437
+ }
3438
+ if (normalized.includes("outbound") || normalized.includes("assistant") || normalized.includes("out")) {
3439
+ return "outbound";
3440
+ }
3441
+ return "unknown";
3442
+ };
3443
+ var telephonyFrameKind = (direction) => direction === "outbound" ? "assistant-audio" : "input-audio";
3387
3444
  var normalizeWebRTCStat = (stat) => {
3388
3445
  const sample = {};
3389
3446
  for (const [key, value] of Object.entries(stat)) {
@@ -3393,6 +3450,113 @@ var normalizeWebRTCStat = (stat) => {
3393
3450
  }
3394
3451
  return sample;
3395
3452
  };
3453
+ var parseTelephonyMediaFrame = (input) => {
3454
+ const envelope = input.envelope;
3455
+ const media = unknownRecord(envelope.media);
3456
+ const payload = firstString([media, envelope], ["payload", "audio", "data"]) ?? firstString([unknownRecord(envelope.message)], ["payload"]);
3457
+ if (!payload) {
3458
+ return;
3459
+ }
3460
+ const carrier = input.carrier ?? firstString([envelope], ["provider"]) ?? "telephony";
3461
+ const streamId = firstString([media, envelope], ["streamSid", "stream_id", "streamId", "streamId", "callSid", "call_id"]);
3462
+ const sequenceNumber = firstString([media, envelope], ["sequenceNumber", "sequence_number", "chunk"]);
3463
+ const track = firstString([media, envelope], ["track", "direction"]);
3464
+ const direction = telephonyDirection(track);
3465
+ const timestamp = firstNumber([media, envelope], ["timestamp", "time", "startedAt"]);
3466
+ return {
3467
+ at: timestamp,
3468
+ audio: base64ToBytes(payload),
3469
+ format: input.format ?? DEFAULT_TELEPHONY_FORMAT,
3470
+ id: [
3471
+ carrier,
3472
+ streamId ?? input.sessionId ?? "stream",
3473
+ sequenceNumber ?? timestamp ?? Date.now()
3474
+ ].join(":"),
3475
+ kind: telephonyFrameKind(direction),
3476
+ metadata: {
3477
+ carrier,
3478
+ direction,
3479
+ event: firstString([envelope], ["event", "type"]),
3480
+ sequenceNumber,
3481
+ streamId,
3482
+ track
3483
+ },
3484
+ sessionId: input.sessionId ?? streamId,
3485
+ source: "telephony"
3486
+ };
3487
+ };
3488
+ var serializeTelephonyMediaFrame = (input) => {
3489
+ const carrier = input.carrier ?? input.frame.metadata?.carrier ?? "telephony";
3490
+ const streamId = input.streamId ?? (typeof input.frame.metadata?.streamId === "string" ? input.frame.metadata.streamId : input.frame.sessionId);
3491
+ const sequenceNumber = input.sequenceNumber ?? (typeof input.frame.metadata?.sequenceNumber === "string" || typeof input.frame.metadata?.sequenceNumber === "number" ? input.frame.metadata.sequenceNumber : undefined);
3492
+ const direction = input.frame.kind === "assistant-audio" ? "outbound" : "inbound";
3493
+ const payload = input.frame.audio ? bytesToBase64(input.frame.audio) : "";
3494
+ if (carrier === "twilio") {
3495
+ return {
3496
+ event: "media",
3497
+ sequenceNumber,
3498
+ streamSid: streamId,
3499
+ media: {
3500
+ payload,
3501
+ timestamp: input.frame.at,
3502
+ track: direction
3503
+ }
3504
+ };
3505
+ }
3506
+ if (carrier === "telnyx") {
3507
+ return {
3508
+ event: "media",
3509
+ stream_id: streamId,
3510
+ sequence_number: sequenceNumber,
3511
+ media: {
3512
+ payload,
3513
+ timestamp: input.frame.at,
3514
+ track: direction
3515
+ }
3516
+ };
3517
+ }
3518
+ if (carrier === "plivo") {
3519
+ return {
3520
+ event: "media",
3521
+ streamId,
3522
+ sequenceNumber,
3523
+ media: {
3524
+ payload,
3525
+ timestamp: input.frame.at,
3526
+ track: direction
3527
+ }
3528
+ };
3529
+ }
3530
+ return {
3531
+ event: "media",
3532
+ provider: carrier,
3533
+ sequenceNumber,
3534
+ streamId,
3535
+ media: {
3536
+ payload,
3537
+ timestamp: input.frame.at,
3538
+ track: direction
3539
+ }
3540
+ };
3541
+ };
3542
+ var createTelephonyMediaSerializer = (input) => {
3543
+ const format = input.format ?? DEFAULT_TELEPHONY_FORMAT;
3544
+ return {
3545
+ carrier: input.carrier,
3546
+ format,
3547
+ parse: (envelope) => parseTelephonyMediaFrame({
3548
+ carrier: input.carrier,
3549
+ envelope,
3550
+ format,
3551
+ sessionId: input.sessionId ?? input.streamId
3552
+ }),
3553
+ serialize: (frame) => serializeTelephonyMediaFrame({
3554
+ carrier: input.carrier,
3555
+ frame,
3556
+ streamId: input.streamId
3557
+ })
3558
+ };
3559
+ };
3396
3560
  var buildMediaResamplingPlan = (input) => {
3397
3561
  const required = !formatMatches(input.inputFormat, input.outputFormat);
3398
3562
  return {
@@ -0,0 +1,63 @@
1
+ import { Elysia } from 'elysia';
2
+ import type { MediaFrame, MediaTelephonyCarrier, MediaTelephonyEnvelope } from '@absolutejs/media';
3
+ export type VoiceTelephonyMediaStatus = 'fail' | 'pass';
4
+ export type VoiceTelephonyMediaCarrierInput = {
5
+ carrier: MediaTelephonyCarrier;
6
+ envelope?: MediaTelephonyEnvelope;
7
+ };
8
+ export type VoiceTelephonyMediaCarrierReport = {
9
+ audioBytes: number;
10
+ carrier: MediaTelephonyCarrier;
11
+ frame?: MediaFrame;
12
+ issues: string[];
13
+ serialized?: MediaTelephonyEnvelope;
14
+ status: VoiceTelephonyMediaStatus;
15
+ };
16
+ export type VoiceTelephonyMediaReport = {
17
+ carriers: readonly VoiceTelephonyMediaCarrierReport[];
18
+ checkedAt: number;
19
+ issues: string[];
20
+ status: VoiceTelephonyMediaStatus;
21
+ };
22
+ export type VoiceTelephonyMediaRoutesOptions = {
23
+ carriers?: readonly VoiceTelephonyMediaCarrierInput[];
24
+ headers?: HeadersInit;
25
+ htmlPath?: false | string;
26
+ name?: string;
27
+ path?: string;
28
+ title?: string;
29
+ };
30
+ export declare const buildVoiceTelephonyMediaReport: (input?: {
31
+ carriers?: readonly VoiceTelephonyMediaCarrierInput[];
32
+ }) => VoiceTelephonyMediaReport;
33
+ export declare const renderVoiceTelephonyMediaHTML: (report: VoiceTelephonyMediaReport, options?: {
34
+ title?: string;
35
+ }) => string;
36
+ export declare const createVoiceTelephonyMediaRoutes: (options?: VoiceTelephonyMediaRoutesOptions) => Elysia<"", {
37
+ decorator: {};
38
+ store: {};
39
+ derive: {};
40
+ resolve: {};
41
+ }, {
42
+ typebox: {};
43
+ error: {};
44
+ }, {
45
+ schema: {};
46
+ standaloneSchema: {};
47
+ macro: {};
48
+ macroFn: {};
49
+ parser: {};
50
+ response: {};
51
+ }, {}, {
52
+ derive: {};
53
+ resolve: {};
54
+ schema: {};
55
+ standaloneSchema: {};
56
+ response: {};
57
+ }, {
58
+ derive: {};
59
+ resolve: {};
60
+ schema: {};
61
+ standaloneSchema: {};
62
+ response: {};
63
+ }>;