@absolutejs/voice 0.0.22-beta.464 → 0.0.22-beta.466

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.
@@ -2159,934 +2159,12 @@ var serverMessageToAction = (message) => {
2159
2159
  }
2160
2160
  };
2161
2161
 
2162
- // node_modules/@absolutejs/media/dist/index.js
2163
- import { mkdir, writeFile } from "fs/promises";
2164
- import { join } from "path";
2165
- var formatLabel = (format) => `${format.container}/${format.encoding}/${String(format.sampleRateHz)}hz/${String(format.channels)}ch`;
2166
- var formatMatches = (actual, expected) => actual.container === expected.container && actual.encoding === expected.encoding && actual.sampleRateHz === expected.sampleRateHz && actual.channels === expected.channels;
2167
- var pushIssue = (issues, severity, code, message) => {
2168
- issues.push({ code, message, severity });
2169
- };
2170
- var numericMetadata = (frame, key) => {
2171
- const value = frame.metadata?.[key];
2172
- return typeof value === "number" && Number.isFinite(value) ? value : undefined;
2173
- };
2174
- var average3 = (values) => values.length === 0 ? undefined : values.reduce((total, value) => total + value, 0) / values.length;
2175
- var max = (values) => values.length === 0 ? undefined : Math.max(...values);
2176
- var min = (values) => values.length === 0 ? undefined : Math.min(...values);
2177
- var numericStat = (stat, key) => {
2178
- const value = stat[key];
2179
- return typeof value === "number" && Number.isFinite(value) ? value : undefined;
2180
- };
2181
- var booleanStat = (stat, key) => {
2182
- const value = stat[key];
2183
- return typeof value === "boolean" ? value : undefined;
2184
- };
2185
- var stringStat = (stat, key) => {
2186
- const value = stat[key];
2187
- return typeof value === "string" ? value : undefined;
2188
- };
2189
- var statKey = (stat) => String(stat.id ?? stringStat(stat, "ssrc") ?? numericStat(stat, "ssrc") ?? stringStat(stat, "trackIdentifier") ?? stringStat(stat, "mid") ?? "unknown");
2190
- var secondsToMs = (value) => value === undefined ? undefined : value * 1000;
2191
- var DEFAULT_TELEPHONY_FORMAT = {
2192
- channels: 1,
2193
- container: "raw",
2194
- encoding: "mulaw",
2195
- sampleRateHz: 8000
2196
- };
2197
- var bytesToBase64 = (audio) => {
2198
- const bytes = audio instanceof ArrayBuffer ? new Uint8Array(audio) : new Uint8Array(audio.buffer, audio.byteOffset, audio.byteLength);
2199
- return Buffer.from(bytes).toString("base64");
2200
- };
2201
- var base64ToBytes = (value) => new Uint8Array(Buffer.from(value, "base64"));
2202
- var unknownRecord = (value) => value && typeof value === "object" ? value : {};
2203
- var firstString = (records, keys) => {
2204
- for (const record of records) {
2205
- for (const key of keys) {
2206
- const value = record[key];
2207
- if (typeof value === "string" && value.length > 0) {
2208
- return value;
2209
- }
2210
- if (typeof value === "number" && Number.isFinite(value)) {
2211
- return String(value);
2212
- }
2213
- }
2214
- }
2215
- return;
2216
- };
2217
- var firstNumber = (records, keys) => {
2218
- for (const record of records) {
2219
- for (const key of keys) {
2220
- const value = record[key];
2221
- if (typeof value === "number" && Number.isFinite(value)) {
2222
- return value;
2223
- }
2224
- if (typeof value === "string") {
2225
- const parsed = Number(value);
2226
- if (Number.isFinite(parsed)) {
2227
- return parsed;
2228
- }
2229
- }
2230
- }
2231
- }
2232
- return;
2233
- };
2234
- var telephonyDirection = (track) => {
2235
- const normalized = track?.toLowerCase();
2236
- if (!normalized) {
2237
- return "unknown";
2238
- }
2239
- if (normalized.includes("inbound") || normalized.includes("caller") || normalized.includes("in")) {
2240
- return "inbound";
2241
- }
2242
- if (normalized.includes("outbound") || normalized.includes("assistant") || normalized.includes("out")) {
2243
- return "outbound";
2244
- }
2245
- return "unknown";
2246
- };
2247
- var telephonyFrameKind = (direction) => direction === "outbound" ? "assistant-audio" : "input-audio";
2248
- var telephonyEventKind = (envelope) => {
2249
- const raw = firstString([envelope], ["event", "type", "eventType"]) ?? firstString([unknownRecord(envelope.message)], ["event", "type"]);
2250
- const normalized = raw?.toLowerCase().replace(/[_\s-]+/g, "-");
2251
- if (!normalized) {
2252
- return "unknown";
2253
- }
2254
- if (normalized.includes("connected")) {
2255
- return "connected";
2256
- }
2257
- if (normalized.includes("start")) {
2258
- return "start";
2259
- }
2260
- if (normalized.includes("media")) {
2261
- return "media";
2262
- }
2263
- if (normalized.includes("stop") || normalized.includes("closed")) {
2264
- return "stop";
2265
- }
2266
- if (normalized.includes("error") || normalized.includes("failed")) {
2267
- return "error";
2268
- }
2269
- return "unknown";
2270
- };
2271
- var normalizeWebRTCStat = (stat) => {
2272
- const sample = {};
2273
- for (const [key, value] of Object.entries(stat)) {
2274
- if (value === null || typeof value === "boolean" || typeof value === "number" || typeof value === "string") {
2275
- sample[key] = value;
2276
- }
2277
- }
2278
- return sample;
2279
- };
2280
- var parseTelephonyMediaFrame = (input) => {
2281
- const envelope = input.envelope;
2282
- const media = unknownRecord(envelope.media);
2283
- const payload = firstString([media, envelope], ["payload", "audio", "data"]) ?? firstString([unknownRecord(envelope.message)], ["payload"]);
2284
- if (!payload) {
2285
- return;
2286
- }
2287
- const carrier = input.carrier ?? firstString([envelope], ["provider"]) ?? "telephony";
2288
- const streamId = firstString([media, envelope], ["streamSid", "stream_id", "streamId", "streamId", "callSid", "call_id"]);
2289
- const sequenceNumber = firstString([media, envelope], ["sequenceNumber", "sequence_number", "chunk"]);
2290
- const track = firstString([media, envelope], ["track", "direction"]);
2291
- const direction = telephonyDirection(track);
2292
- const timestamp = firstNumber([media, envelope], ["timestamp", "time", "startedAt"]);
2293
- return {
2294
- at: timestamp,
2295
- audio: base64ToBytes(payload),
2296
- format: input.format ?? DEFAULT_TELEPHONY_FORMAT,
2297
- id: [
2298
- carrier,
2299
- streamId ?? input.sessionId ?? "stream",
2300
- sequenceNumber ?? timestamp ?? Date.now()
2301
- ].join(":"),
2302
- kind: telephonyFrameKind(direction),
2303
- metadata: {
2304
- carrier,
2305
- direction,
2306
- event: firstString([envelope], ["event", "type"]),
2307
- sequenceNumber,
2308
- streamId,
2309
- track
2310
- },
2311
- sessionId: input.sessionId ?? streamId,
2312
- source: "telephony"
2313
- };
2314
- };
2315
- var serializeTelephonyMediaFrame = (input) => {
2316
- const carrier = input.carrier ?? input.frame.metadata?.carrier ?? "telephony";
2317
- const streamId = input.streamId ?? (typeof input.frame.metadata?.streamId === "string" ? input.frame.metadata.streamId : input.frame.sessionId);
2318
- const sequenceNumber = input.sequenceNumber ?? (typeof input.frame.metadata?.sequenceNumber === "string" || typeof input.frame.metadata?.sequenceNumber === "number" ? input.frame.metadata.sequenceNumber : undefined);
2319
- const direction = input.frame.kind === "assistant-audio" ? "outbound" : "inbound";
2320
- const payload = input.frame.audio ? bytesToBase64(input.frame.audio) : "";
2321
- if (carrier === "twilio") {
2322
- return {
2323
- event: "media",
2324
- sequenceNumber,
2325
- streamSid: streamId,
2326
- media: {
2327
- payload,
2328
- timestamp: input.frame.at,
2329
- track: direction
2330
- }
2331
- };
2332
- }
2333
- if (carrier === "telnyx") {
2334
- return {
2335
- event: "media",
2336
- stream_id: streamId,
2337
- sequence_number: sequenceNumber,
2338
- media: {
2339
- payload,
2340
- timestamp: input.frame.at,
2341
- track: direction
2342
- }
2343
- };
2344
- }
2345
- if (carrier === "plivo") {
2346
- return {
2347
- event: "media",
2348
- streamId,
2349
- sequenceNumber,
2350
- media: {
2351
- payload,
2352
- timestamp: input.frame.at,
2353
- track: direction
2354
- }
2355
- };
2356
- }
2357
- return {
2358
- event: "media",
2359
- provider: carrier,
2360
- sequenceNumber,
2361
- streamId,
2362
- media: {
2363
- payload,
2364
- timestamp: input.frame.at,
2365
- track: direction
2366
- }
2367
- };
2368
- };
2369
- var createTelephonyMediaSerializer = (input) => {
2370
- const format = input.format ?? DEFAULT_TELEPHONY_FORMAT;
2371
- return {
2372
- carrier: input.carrier,
2373
- format,
2374
- parse: (envelope) => parseTelephonyMediaFrame({
2375
- carrier: input.carrier,
2376
- envelope,
2377
- format,
2378
- sessionId: input.sessionId ?? input.streamId
2379
- }),
2380
- serialize: (frame) => serializeTelephonyMediaFrame({
2381
- carrier: input.carrier,
2382
- frame,
2383
- streamId: input.streamId
2384
- })
2385
- };
2386
- };
2387
- var parseTelephonyStreamEvent = (input) => {
2388
- const envelope = input.envelope;
2389
- const media = unknownRecord(envelope.media);
2390
- const start = unknownRecord(envelope.start);
2391
- const stop = unknownRecord(envelope.stop);
2392
- const errorRecord = unknownRecord(envelope.error);
2393
- const kind = telephonyEventKind(envelope);
2394
- const carrier = input.carrier ?? firstString([envelope], ["provider", "carrier"]) ?? "telephony";
2395
- const frame = kind === "media" ? parseTelephonyMediaFrame({
2396
- carrier,
2397
- envelope,
2398
- format: input.format,
2399
- sessionId: input.sessionId
2400
- }) : undefined;
2401
- const streamId = firstString([media, start, stop, envelope], ["streamSid", "stream_id", "streamId", "callSid", "call_id"]) ?? input.sessionId;
2402
- const sequenceNumber = firstString([media, envelope], ["sequenceNumber", "sequence_number", "chunk"]);
2403
- const track = firstString([media, envelope], ["track", "direction"]);
2404
- return {
2405
- audioBytes: frame?.audio ? frame.audio instanceof ArrayBuffer ? frame.audio.byteLength : frame.audio.byteLength : 0,
2406
- at: frame?.at ?? firstNumber([media, start, stop, envelope], ["timestamp", "time", "startedAt"]),
2407
- carrier,
2408
- direction: telephonyDirection(track),
2409
- error: firstString([errorRecord, envelope], ["message", "error", "reason"]),
2410
- kind,
2411
- sequenceNumber,
2412
- streamId
2413
- };
2414
- };
2415
- var buildMediaTelephonyStreamLifecycleReport = (input = {}) => {
2416
- const envelopes = input.envelopes ?? [];
2417
- const events = envelopes.map((envelope) => parseTelephonyStreamEvent({
2418
- carrier: input.carrier,
2419
- envelope
2420
- }));
2421
- const issues = [];
2422
- const startedIndex = events.findIndex((event) => event.kind === "start");
2423
- const firstMediaIndex = events.findIndex((event) => event.kind === "media");
2424
- const stoppedIndex = events.findIndex((event) => event.kind === "stop");
2425
- const started = startedIndex >= 0;
2426
- const stopped = stoppedIndex >= 0;
2427
- const mediaEvents = events.filter((event) => event.kind === "media");
2428
- const audioBytes = events.reduce((total, event) => total + event.audioBytes, 0);
2429
- const minAudioBytes = input.minAudioBytes ?? 1;
2430
- const streamIds = Array.from(new Set(events.map((event) => event.streamId).filter(Boolean)));
2431
- if ((input.requireStart ?? true) && !started) {
2432
- pushIssue(issues, "error", "media.telephony_missing_start", "Telephony media stream did not include a start event.");
2433
- }
2434
- if ((input.requireMedia ?? true) && mediaEvents.length === 0) {
2435
- pushIssue(issues, "error", "media.telephony_missing_media", "Telephony media stream did not include media payload events.");
2436
- }
2437
- if ((input.requireStop ?? true) && !stopped) {
2438
- pushIssue(issues, input.maxMissingStop === false ? "warning" : "error", "media.telephony_missing_stop", "Telephony media stream did not include a stop event.");
2439
- }
2440
- if (started && firstMediaIndex >= 0 && firstMediaIndex < startedIndex) {
2441
- pushIssue(issues, "error", "media.telephony_media_before_start", "Telephony media payload arrived before the stream start event.");
2442
- }
2443
- if (stopped && firstMediaIndex >= 0 && stoppedIndex < firstMediaIndex) {
2444
- pushIssue(issues, "error", "media.telephony_stop_before_media", "Telephony media stream stopped before any media payload arrived.");
2445
- }
2446
- if (mediaEvents.length > 0 && audioBytes < minAudioBytes) {
2447
- pushIssue(issues, "error", "media.telephony_no_audio_bytes", `Telephony media stream parsed ${String(audioBytes)} audio byte(s), below required ${String(minAudioBytes)}.`);
2448
- }
2449
- for (const event of events) {
2450
- if (event.kind === "error") {
2451
- pushIssue(issues, "error", "media.telephony_stream_error", event.error ?? "Telephony media stream emitted an error event.");
2452
- }
2453
- }
2454
- return {
2455
- audioBytes,
2456
- carrier: input.carrier,
2457
- checkedAt: Date.now(),
2458
- events,
2459
- issues,
2460
- mediaEvents: mediaEvents.length,
2461
- started,
2462
- status: issues.some((issue) => issue.severity === "error") ? "fail" : issues.length > 0 ? "warn" : "pass",
2463
- stopped,
2464
- streamIds
2465
- };
2466
- };
2467
- var buildMediaResamplingPlan = (input) => {
2468
- const required = !formatMatches(input.inputFormat, input.outputFormat);
2469
- return {
2470
- inputFormat: input.inputFormat,
2471
- outputFormat: input.outputFormat,
2472
- ratio: input.outputFormat.sampleRateHz / input.inputFormat.sampleRateHz,
2473
- required,
2474
- status: input.inputFormat.container === input.outputFormat.container && input.inputFormat.encoding === input.outputFormat.encoding && input.inputFormat.channels === input.outputFormat.channels ? "pass" : "warn"
2475
- };
2476
- };
2477
- var speechProbability = (frame) => {
2478
- if (frame.metadata?.isSpeech === true) {
2479
- return 1;
2480
- }
2481
- if (frame.metadata?.isSpeech === false) {
2482
- return 0;
2483
- }
2484
- for (const key of ["speechProbability", "voiceProbability", "rms", "energy"]) {
2485
- const value = numericMetadata(frame, key);
2486
- if (value !== undefined) {
2487
- return value;
2488
- }
2489
- }
2490
- return 0;
2491
- };
2492
- var buildMediaVadReport = (input = {}) => {
2493
- const frames = (input.frames ?? []).filter((frame) => frame.kind === "input-audio");
2494
- const speechStartThreshold = input.speechStartThreshold ?? 0.6;
2495
- const speechEndThreshold = input.speechEndThreshold ?? 0.35;
2496
- const minSpeechFrames = input.minSpeechFrames ?? 1;
2497
- const maxSilenceFrames = input.maxSilenceFrames ?? 1;
2498
- const segments = [];
2499
- let activeFrames = [];
2500
- let silenceFrames = 0;
2501
- const closeSegment = () => {
2502
- if (activeFrames.length < minSpeechFrames) {
2503
- activeFrames = [];
2504
- silenceFrames = 0;
2505
- return;
2506
- }
2507
- const first = activeFrames[0];
2508
- const last = activeFrames.at(-1);
2509
- if (!first) {
2510
- return;
2511
- }
2512
- segments.push({
2513
- durationMs: first.at !== undefined && last?.at !== undefined ? last.at - first.at + (last.durationMs ?? 0) : undefined,
2514
- endAt: last?.at !== undefined ? last.at + (last.durationMs ?? 0) : undefined,
2515
- frameCount: activeFrames.length,
2516
- segmentId: `vad:${String(segments.length + 1)}`,
2517
- sessionId: first.sessionId,
2518
- startAt: first.at,
2519
- turnId: first.turnId
2520
- });
2521
- activeFrames = [];
2522
- silenceFrames = 0;
2523
- };
2524
- for (const frame of frames) {
2525
- const probability = speechProbability(frame);
2526
- if (activeFrames.length === 0) {
2527
- if (probability >= speechStartThreshold) {
2528
- activeFrames.push(frame);
2529
- }
2530
- continue;
2531
- }
2532
- activeFrames.push(frame);
2533
- if (probability <= speechEndThreshold) {
2534
- silenceFrames += 1;
2535
- } else {
2536
- silenceFrames = 0;
2537
- }
2538
- if (silenceFrames > maxSilenceFrames) {
2539
- closeSegment();
2540
- }
2541
- }
2542
- closeSegment();
2543
- return {
2544
- checkedAt: Date.now(),
2545
- inputAudioFrames: frames.length,
2546
- segments,
2547
- status: frames.length === 0 ? "warn" : "pass"
2548
- };
2549
- };
2550
- var buildMediaInterruptionReport = (input = {}) => {
2551
- const issues = [];
2552
- const interruptionFrames = (input.frames ?? []).filter((frame) => frame.kind === "interruption");
2553
- const latenciesMs = interruptionFrames.map((frame) => frame.latencyMs).filter((latency) => typeof latency === "number");
2554
- const maxInterruptionLatencyMs = input.maxInterruptionLatencyMs;
2555
- if (interruptionFrames.length === 0) {
2556
- pushIssue(issues, "warning", "media.interruption_missing", "No interruption frame was observed.");
2557
- }
2558
- if (maxInterruptionLatencyMs !== undefined && latenciesMs.some((latency) => latency > maxInterruptionLatencyMs)) {
2559
- pushIssue(issues, "error", "media.interruption_latency", `Interruption latency exceeded ${String(maxInterruptionLatencyMs)}ms.`);
2560
- }
2561
- return {
2562
- checkedAt: Date.now(),
2563
- interruptionFrames: interruptionFrames.length,
2564
- issues,
2565
- latenciesMs,
2566
- status: issues.some((issue) => issue.severity === "error") ? "fail" : issues.length > 0 ? "warn" : "pass"
2567
- };
2568
- };
2569
- var buildMediaQualityReport = (input = {}) => {
2570
- const frames = [...input.frames ?? []].sort((a, b) => (a.at ?? 0) - (b.at ?? 0));
2571
- const audioFrames = frames.filter((frame) => frame.kind === "input-audio" || frame.kind === "assistant-audio");
2572
- const inputAudioFrames = frames.filter((frame) => frame.kind === "input-audio");
2573
- const assistantAudioFrames = frames.filter((frame) => frame.kind === "assistant-audio");
2574
- const issues = [];
2575
- const gapsMs = [];
2576
- for (const [index, frame] of audioFrames.entries()) {
2577
- const previous = audioFrames[index - 1];
2578
- if (previous?.at === undefined || frame.at === undefined || previous.durationMs === undefined) {
2579
- continue;
2580
- }
2581
- const gap = frame.at - (previous.at + previous.durationMs);
2582
- if (gap > 0) {
2583
- gapsMs.push(gap);
2584
- }
2585
- }
2586
- const jitterMs = audioFrames.map((frame) => numericMetadata(frame, "jitterMs")).filter((value) => value !== undefined).at(-1) ?? max(gapsMs);
2587
- const first = audioFrames.find((frame) => frame.at !== undefined);
2588
- const last = audioFrames.toReversed().find((frame) => frame.at !== undefined);
2589
- const durationMs = first?.at !== undefined && last?.at !== undefined ? last.at - first.at + (last.durationMs ?? 0) : undefined;
2590
- const expectedDurationMs = audioFrames.length > 0 ? audioFrames.reduce((total, frame) => total + (frame.durationMs ?? 0), 0) : undefined;
2591
- const timestampDriftMs = durationMs !== undefined && expectedDurationMs !== undefined ? Math.max(0, durationMs - expectedDurationMs) : undefined;
2592
- const speechScores = inputAudioFrames.map(speechProbability);
2593
- const speechFrames = speechScores.filter((score) => score >= 0.6).length;
2594
- const silenceFrames = speechScores.filter((score) => score <= 0.35).length;
2595
- const unknownSpeechFrames = Math.max(0, inputAudioFrames.length - speechFrames - silenceFrames);
2596
- const speechRatio = inputAudioFrames.length === 0 ? 0 : speechFrames / inputAudioFrames.length;
2597
- const silenceRatio = inputAudioFrames.length === 0 ? 0 : silenceFrames / inputAudioFrames.length;
2598
- const levels = audioFrames.map((frame) => numericMetadata(frame, "level") ?? numericMetadata(frame, "rms") ?? numericMetadata(frame, "energy")).filter((value) => value !== undefined);
2599
- const backpressureEvents = input.transport?.backpressureEvents ?? 0;
2600
- const maxGapMs = input.maxGapMs;
2601
- if (maxGapMs !== undefined && gapsMs.some((gap) => gap > maxGapMs)) {
2602
- pushIssue(issues, "warning", "media.quality_gap", `Observed media gap above ${String(maxGapMs)}ms.`);
2603
- }
2604
- if (input.maxJitterMs !== undefined && jitterMs !== undefined && jitterMs > input.maxJitterMs) {
2605
- pushIssue(issues, "warning", "media.quality_jitter", `Observed jitter ${String(jitterMs)}ms above ${String(input.maxJitterMs)}ms.`);
2606
- }
2607
- if (input.maxTimestampDriftMs !== undefined && timestampDriftMs !== undefined && timestampDriftMs > input.maxTimestampDriftMs) {
2608
- pushIssue(issues, "warning", "media.quality_timestamp_drift", `Observed timestamp drift ${String(timestampDriftMs)}ms above ${String(input.maxTimestampDriftMs)}ms.`);
2609
- }
2610
- if (input.minSpeechRatio !== undefined && inputAudioFrames.length > 0 && speechRatio < input.minSpeechRatio) {
2611
- pushIssue(issues, "warning", "media.quality_speech_ratio", `Observed speech ratio ${String(speechRatio)} below ${String(input.minSpeechRatio)}.`);
2612
- }
2613
- if (input.maxBackpressureEvents !== undefined && backpressureEvents > input.maxBackpressureEvents) {
2614
- pushIssue(issues, "warning", "media.quality_backpressure", `Observed ${String(backpressureEvents)} backpressure event(s), above ${String(input.maxBackpressureEvents)}.`);
2615
- }
2616
- return {
2617
- assistantAudioFrames: assistantAudioFrames.length,
2618
- backpressureEvents,
2619
- checkedAt: Date.now(),
2620
- durationMs,
2621
- gapCount: gapsMs.length,
2622
- gapsMs,
2623
- inputAudioFrames: inputAudioFrames.length,
2624
- issues,
2625
- jitterMs,
2626
- levelAverage: average3(levels),
2627
- levelMax: max(levels),
2628
- levelMin: min(levels),
2629
- silenceFrames,
2630
- silenceRatio,
2631
- speechFrames,
2632
- speechRatio,
2633
- status: issues.some((issue) => issue.severity === "error") ? "fail" : issues.length > 0 ? "warn" : "pass",
2634
- timestampDriftMs,
2635
- totalFrames: frames.length,
2636
- unknownSpeechFrames
2637
- };
2638
- };
2639
- var buildMediaWebRTCStatsReport = (input = {}) => {
2640
- const stats = input.stats ?? [];
2641
- const issues = [];
2642
- const inbound = stats.filter((stat) => stat.type === "inbound-rtp" && stringStat(stat, "kind") !== "video");
2643
- const outbound = stats.filter((stat) => stat.type === "outbound-rtp" && stringStat(stat, "kind") !== "video");
2644
- const candidatePairs = stats.filter((stat) => stat.type === "candidate-pair");
2645
- const audioTracks = stats.filter((stat) => (stat.type === "track" || stat.type === "media-source") && stringStat(stat, "kind") === "audio");
2646
- const activeCandidatePairs = candidatePairs.filter((stat) => booleanStat(stat, "selected") === true || booleanStat(stat, "nominated") === true || stringStat(stat, "state") === "succeeded").length;
2647
- const liveAudioTracks = audioTracks.filter((stat) => stringStat(stat, "readyState") !== "ended" && stringStat(stat, "trackState") !== "ended" && booleanStat(stat, "ended") !== true).length;
2648
- const endedAudioTracks = audioTracks.filter((stat) => stringStat(stat, "readyState") === "ended" || stringStat(stat, "trackState") === "ended" || booleanStat(stat, "ended") === true).length;
2649
- const inboundPackets = inbound.reduce((total, stat) => total + (numericStat(stat, "packetsReceived") ?? 0), 0);
2650
- const outboundPackets = outbound.reduce((total, stat) => total + (numericStat(stat, "packetsSent") ?? 0), 0);
2651
- const packetsLost = [...inbound, ...outbound].reduce((total, stat) => total + Math.max(0, numericStat(stat, "packetsLost") ?? 0), 0);
2652
- const packetLossDenominator = inboundPackets + packetsLost;
2653
- const packetLossRatio = packetLossDenominator === 0 ? 0 : packetsLost / packetLossDenominator;
2654
- const bytesReceived = inbound.reduce((total, stat) => total + (numericStat(stat, "bytesReceived") ?? 0), 0);
2655
- const bytesSent = outbound.reduce((total, stat) => total + (numericStat(stat, "bytesSent") ?? 0), 0);
2656
- const roundTripTimeMs = max(candidatePairs.map((stat) => secondsToMs(numericStat(stat, "currentRoundTripTime") ?? numericStat(stat, "roundTripTime"))).filter((value) => value !== undefined));
2657
- const jitterMs = max([...inbound, ...outbound].map((stat) => secondsToMs(numericStat(stat, "jitter"))).filter((value) => value !== undefined));
2658
- const jitterBufferDelayMs = max(inbound.map((stat) => {
2659
- const delay = numericStat(stat, "jitterBufferDelay");
2660
- const emitted = numericStat(stat, "jitterBufferEmittedCount");
2661
- return delay !== undefined && emitted !== undefined && emitted > 0 ? delay / emitted * 1000 : undefined;
2662
- }).filter((value) => value !== undefined));
2663
- const audioLevels = audioTracks.map((stat) => numericStat(stat, "audioLevel")).filter((value) => value !== undefined);
2664
- if (input.requireConnectedCandidatePair && candidatePairs.length > 0 && activeCandidatePairs === 0) {
2665
- pushIssue(issues, "error", "media.webrtc_candidate_pair_missing", "No active WebRTC candidate pair was observed.");
2666
- }
2667
- if (input.requireLiveAudioTrack && liveAudioTracks === 0) {
2668
- pushIssue(issues, "error", "media.webrtc_audio_track_missing", "No live WebRTC audio track was observed.");
2669
- }
2670
- if (input.maxPacketLossRatio !== undefined && packetLossRatio > input.maxPacketLossRatio) {
2671
- pushIssue(issues, "warning", "media.webrtc_packet_loss", `Observed WebRTC packet loss ratio ${String(packetLossRatio)} above ${String(input.maxPacketLossRatio)}.`);
2672
- }
2673
- if (input.maxRoundTripTimeMs !== undefined && roundTripTimeMs !== undefined && roundTripTimeMs > input.maxRoundTripTimeMs) {
2674
- pushIssue(issues, "warning", "media.webrtc_round_trip_time", `Observed WebRTC RTT ${String(roundTripTimeMs)}ms above ${String(input.maxRoundTripTimeMs)}ms.`);
2675
- }
2676
- if (input.maxJitterMs !== undefined && jitterMs !== undefined && jitterMs > input.maxJitterMs) {
2677
- pushIssue(issues, "warning", "media.webrtc_jitter", `Observed WebRTC jitter ${String(jitterMs)}ms above ${String(input.maxJitterMs)}ms.`);
2678
- }
2679
- return {
2680
- activeCandidatePairs,
2681
- audioLevelAverage: average3(audioLevels),
2682
- bytesReceived,
2683
- bytesSent,
2684
- checkedAt: Date.now(),
2685
- endedAudioTracks,
2686
- inboundPackets,
2687
- issues,
2688
- jitterBufferDelayMs,
2689
- jitterMs,
2690
- liveAudioTracks,
2691
- outboundPackets,
2692
- packetLossRatio,
2693
- packetsLost,
2694
- roundTripTimeMs,
2695
- status: issues.some((issue) => issue.severity === "error") ? "fail" : issues.length > 0 ? "warn" : "pass",
2696
- totalStats: stats.length
2697
- };
2698
- };
2699
- var collectMediaWebRTCStats = async (input) => {
2700
- const report = await input.peerConnection.getStats(input.selector ?? null);
2701
- return [...report.values()].map(normalizeWebRTCStat);
2702
- };
2703
- var buildMediaWebRTCStreamContinuityReport = (input = {}) => {
2704
- const stats = input.stats ?? [];
2705
- const previousStats = input.previousStats ?? [];
2706
- const issues = [];
2707
- const previousByKey = new Map(previousStats.map((stat) => [statKey(stat), stat]));
2708
- const audioRtp = stats.filter((stat) => (stat.type === "inbound-rtp" || stat.type === "outbound-rtp") && stringStat(stat, "kind") !== "video" && stringStat(stat, "mediaType") !== "video");
2709
- const streams = audioRtp.map((stat) => {
2710
- const direction = stat.type === "outbound-rtp" ? "outbound" : "inbound";
2711
- const packetsKey = direction === "outbound" ? "packetsSent" : "packetsReceived";
2712
- const bytesKey = direction === "outbound" ? "bytesSent" : "bytesReceived";
2713
- const previous = previousByKey.get(statKey(stat));
2714
- const currentPackets = numericStat(stat, packetsKey);
2715
- const previousPackets = previous ? numericStat(previous, packetsKey) : undefined;
2716
- const currentBytes = numericStat(stat, bytesKey);
2717
- const previousBytes = previous ? numericStat(previous, bytesKey) : undefined;
2718
- const timeDeltaMs = stat.timestamp !== undefined && previous?.timestamp !== undefined ? stat.timestamp - previous.timestamp : undefined;
2719
- return {
2720
- bytesDelta: currentBytes !== undefined && previousBytes !== undefined ? currentBytes - previousBytes : undefined,
2721
- currentPackets,
2722
- direction,
2723
- id: statKey(stat),
2724
- packetDelta: currentPackets !== undefined && previousPackets !== undefined ? currentPackets - previousPackets : undefined,
2725
- previousPackets,
2726
- timeDeltaMs
2727
- };
2728
- });
2729
- const inbound = streams.filter((stream) => stream.direction === "inbound");
2730
- const outbound = streams.filter((stream) => stream.direction === "outbound");
2731
- const maxObservedGapMs = max(streams.map((stream) => stream.timeDeltaMs).filter((value) => value !== undefined));
2732
- const stalledInboundStreams = inbound.filter((stream) => input.maxInboundPacketStallMs !== undefined && stream.timeDeltaMs !== undefined && stream.timeDeltaMs >= input.maxInboundPacketStallMs && stream.packetDelta !== undefined && stream.packetDelta <= 0).length;
2733
- const stalledOutboundStreams = outbound.filter((stream) => input.maxOutboundPacketStallMs !== undefined && stream.timeDeltaMs !== undefined && stream.timeDeltaMs >= input.maxOutboundPacketStallMs && stream.packetDelta !== undefined && stream.packetDelta <= 0).length;
2734
- if (input.requireInboundAudio && inbound.length === 0) {
2735
- pushIssue(issues, "error", "media.webrtc_inbound_audio_missing", "No inbound WebRTC audio RTP stream was observed.");
2736
- }
2737
- if (input.requireOutboundAudio && outbound.length === 0) {
2738
- pushIssue(issues, "error", "media.webrtc_outbound_audio_missing", "No outbound WebRTC audio RTP stream was observed.");
2739
- }
2740
- if (input.maxGapMs !== undefined && maxObservedGapMs !== undefined && maxObservedGapMs > input.maxGapMs) {
2741
- pushIssue(issues, "warning", "media.webrtc_stream_gap", `Observed WebRTC stream sample gap ${String(maxObservedGapMs)}ms above ${String(input.maxGapMs)}ms.`);
2742
- }
2743
- if (stalledInboundStreams > 0) {
2744
- pushIssue(issues, "error", "media.webrtc_inbound_stalled", `${String(stalledInboundStreams)} inbound WebRTC audio stream(s) stopped receiving packets.`);
2745
- }
2746
- if (stalledOutboundStreams > 0) {
2747
- pushIssue(issues, "error", "media.webrtc_outbound_stalled", `${String(stalledOutboundStreams)} outbound WebRTC audio stream(s) stopped sending packets.`);
2748
- }
2749
- return {
2750
- checkedAt: Date.now(),
2751
- inboundAudioStreams: inbound.length,
2752
- issues,
2753
- maxObservedGapMs,
2754
- outboundAudioStreams: outbound.length,
2755
- stalledInboundStreams,
2756
- stalledOutboundStreams,
2757
- status: issues.some((issue) => issue.severity === "error") ? "fail" : issues.length > 0 ? "warn" : "pass",
2758
- streams,
2759
- totalStats: stats.length
2760
- };
2761
- };
2762
- var buildMediaPipelineCalibrationReport = (input = {}) => {
2763
- const frames = input.frames ?? [];
2764
- const issues = [];
2765
- const inputFrames = frames.filter((frame) => frame.kind === "input-audio");
2766
- const assistantFrames = frames.filter((frame) => frame.kind === "assistant-audio");
2767
- const turnCommitFrames = frames.filter((frame) => frame.kind === "turn-commit");
2768
- const interruptionFrameRecords = frames.filter((frame) => frame.kind === "interruption");
2769
- const traceLinkedFrames = frames.filter((frame) => frame.traceEventId).length;
2770
- const backpressureFrames = frames.filter((frame) => Boolean(frame.metadata?.backpressure)).length;
2771
- const audioLatencies = assistantFrames.map((frame) => frame.latencyMs).filter((latency) => typeof latency === "number");
2772
- const firstAudioLatencyMs = audioLatencies.length > 0 ? Math.min(...audioLatencies) : undefined;
2773
- const jitterValues = frames.map((frame) => numericMetadata(frame, "jitterMs")).filter((value) => value !== undefined);
2774
- const jitterMs = jitterValues.length > 0 ? Math.max(...jitterValues) : undefined;
2775
- const inputFormat = input.inputFormat ?? inputFrames.find((frame) => frame.format)?.format;
2776
- const outputFormat = input.outputFormat ?? assistantFrames.find((frame) => frame.format)?.format;
2777
- const resamplingRequired = Boolean(input.expectedInputFormat && inputFormat && inputFormat.sampleRateHz !== input.expectedInputFormat.sampleRateHz) || Boolean(input.expectedOutputFormat && outputFormat && outputFormat.sampleRateHz !== input.expectedOutputFormat.sampleRateHz);
2778
- const resamplingTargetHz = resamplingRequired && input.expectedInputFormat ? input.expectedInputFormat.sampleRateHz : resamplingRequired ? input.expectedOutputFormat?.sampleRateHz : undefined;
2779
- if (inputFrames.length === 0) {
2780
- pushIssue(issues, "warning", "media.input_audio_missing", "No input audio frames were observed.");
2781
- }
2782
- if (assistantFrames.length === 0) {
2783
- pushIssue(issues, "warning", "media.assistant_audio_missing", "No assistant audio frames were observed.");
2784
- }
2785
- if (input.expectedInputFormat && inputFormat && !formatMatches(inputFormat, input.expectedInputFormat)) {
2786
- pushIssue(issues, inputFormat.sampleRateHz === input.expectedInputFormat.sampleRateHz ? "warning" : "error", "media.input_format_mismatch", `Input format ${formatLabel(inputFormat)} does not match expected ${formatLabel(input.expectedInputFormat)}.`);
2787
- }
2788
- if (input.expectedOutputFormat && outputFormat && !formatMatches(outputFormat, input.expectedOutputFormat)) {
2789
- pushIssue(issues, outputFormat.sampleRateHz === input.expectedOutputFormat.sampleRateHz ? "warning" : "error", "media.output_format_mismatch", `Output format ${formatLabel(outputFormat)} does not match expected ${formatLabel(input.expectedOutputFormat)}.`);
2790
- }
2791
- if (firstAudioLatencyMs !== undefined && input.maxFirstAudioLatencyMs !== undefined && firstAudioLatencyMs > input.maxFirstAudioLatencyMs) {
2792
- pushIssue(issues, "error", "media.first_audio_latency", `First audio latency ${String(firstAudioLatencyMs)}ms exceeds budget ${String(input.maxFirstAudioLatencyMs)}ms.`);
2793
- }
2794
- if (jitterMs !== undefined && input.maxJitterMs !== undefined && jitterMs > input.maxJitterMs) {
2795
- pushIssue(issues, "warning", "media.jitter", `Media jitter ${String(jitterMs)}ms exceeds budget ${String(input.maxJitterMs)}ms.`);
2796
- }
2797
- if (input.maxBackpressureFrames !== undefined && backpressureFrames > input.maxBackpressureFrames) {
2798
- pushIssue(issues, "warning", "media.backpressure", `Backpressure frame count ${String(backpressureFrames)} exceeds budget ${String(input.maxBackpressureFrames)}.`);
2799
- }
2800
- if (input.requireInterruptionFrame && interruptionFrameRecords.length === 0) {
2801
- pushIssue(issues, "warning", "media.interruption_missing", "No interruption frame was observed.");
2802
- }
2803
- if (input.requireTraceEvidence && traceLinkedFrames === 0) {
2804
- pushIssue(issues, "warning", "media.trace_evidence_missing", "No media frames were linked to trace evidence.");
2805
- }
2806
- return {
2807
- assistantAudioFrames: assistantFrames.length,
2808
- backpressureFrames,
2809
- checkedAt: Date.now(),
2810
- firstAudioLatencyMs,
2811
- inputAudioFrames: inputFrames.length,
2812
- inputFormat,
2813
- interruptionFrames: interruptionFrameRecords.length,
2814
- issues,
2815
- jitterMs,
2816
- outputFormat,
2817
- resamplingRequired,
2818
- resamplingTargetHz,
2819
- status: issues.some((issue) => issue.severity === "error") ? "fail" : issues.length > 0 ? "warn" : "pass",
2820
- surface: input.surface ?? "media-pipeline",
2821
- traceLinkedFrames,
2822
- turnCommitFrames: turnCommitFrames.length
2823
- };
2824
- };
2825
- var DEFAULT_METADATA_DENY = [
2826
- "audioPayload",
2827
- "auth",
2828
- "authorization",
2829
- "cookie",
2830
- "email",
2831
- "phone",
2832
- "phoneNumber",
2833
- "rawPayload",
2834
- "secret",
2835
- "token",
2836
- "transcript",
2837
- "utterance"
2838
- ];
2839
- var DEFAULT_TRUNCATE = 8;
2840
- var issueCodes = (issues) => Array.from(new Set(issues.map((issue) => issue.code)));
2841
- var lastEventKind = (events) => events[events.length - 1]?.kind;
2842
- var formatOptionalMs = (value) => value === undefined ? "n/a" : `${String(Math.round(value))}ms`;
2843
- var formatRatio = (value) => `${(value * 100).toFixed(1)}%`;
2844
- var escapeMarkdownCell = (value) => value.replace(/\|/g, "\\|").replace(/\n/g, " ");
2845
- var renderIssuesTable = (issues) => {
2846
- if (issues.length === 0) {
2847
- return `- No issues.
2848
- `;
2849
- }
2850
- const rows = issues.map((issue) => `| ${issue.severity} | ${escapeMarkdownCell(issue.code)} | ${escapeMarkdownCell(issue.message)} |`).join(`
2851
- `);
2852
- return `| Severity | Code | Message |
2853
- | --- | --- | --- |
2854
- ${rows}
2855
- `;
2856
- };
2857
- var summarizeMediaQualityReport = (report) => ({
2858
- backpressureEvents: report.backpressureEvents,
2859
- description: `${report.totalFrames} frame(s), ${report.gapCount} gap(s), speech ${formatRatio(report.speechRatio)}, status ${report.status}.`,
2860
- driftMs: report.timestampDriftMs,
2861
- frameCount: report.totalFrames,
2862
- gapCount: report.gapCount,
2863
- issueCodes: issueCodes(report.issues),
2864
- issueCount: report.issues.length,
2865
- jitterMs: report.jitterMs,
2866
- silenceRatio: report.silenceRatio,
2867
- speechRatio: report.speechRatio,
2868
- status: report.status
2869
- });
2870
- var summarizeMediaTransportReport = (report) => ({
2871
- backpressureEvents: report.backpressureEvents,
2872
- description: `${report.name}: ${report.state}, in ${report.inputFrames}, out ${report.outputFrames}, backpressure ${report.backpressureEvents}.`,
2873
- errors: report.events.filter((event) => event.kind === "error").length,
2874
- inputFrames: report.inputFrames,
2875
- lastEventKind: lastEventKind(report.events),
2876
- name: report.name,
2877
- outputFrames: report.outputFrames,
2878
- state: report.state,
2879
- status: report.status
2880
- });
2881
- var summarizeMediaProcessorGraphReport = (report) => {
2882
- const errorIssueCodes = Array.from(new Set(report.errors.map((event) => event.kind)));
2883
- return {
2884
- backpressureEvents: report.backpressure.events.length,
2885
- description: `${report.name}: ${report.state}, ${report.nodes.length} node(s), in ${report.inputFrames}, out ${report.emittedFrames}, dropped ${report.droppedFrames}.`,
2886
- droppedFrames: report.droppedFrames,
2887
- edgeCount: report.edges.length,
2888
- edgeEventCount: report.edgeEvents.length,
2889
- emittedFrames: report.emittedFrames,
2890
- errorCount: report.errors.length,
2891
- inputFrames: report.inputFrames,
2892
- issueCodes: errorIssueCodes,
2893
- lifecycleEventCount: report.lifecycleEvents.length,
2894
- name: report.name,
2895
- nodeCount: report.nodes.length,
2896
- state: report.state,
2897
- status: report.status,
2898
- timingMaxMs: report.timing.maxNodeMs
2899
- };
2900
- };
2901
- var renderMediaQualityMarkdown = (report, options = {}) => {
2902
- const title = options.title ?? "Media Quality Report";
2903
- const lines = [
2904
- `# ${title}`,
2905
- "",
2906
- `Status: **${report.status}**`,
2907
- "",
2908
- "| Metric | Value |",
2909
- "| --- | ---: |",
2910
- `| Total frames | ${report.totalFrames} |`,
2911
- `| Input audio | ${report.inputAudioFrames} |`,
2912
- `| Assistant audio | ${report.assistantAudioFrames} |`,
2913
- `| Gaps | ${report.gapCount} |`,
2914
- `| Jitter | ${formatOptionalMs(report.jitterMs)} |`,
2915
- `| Timestamp drift | ${formatOptionalMs(report.timestampDriftMs)} |`,
2916
- `| Speech ratio | ${formatRatio(report.speechRatio)} |`,
2917
- `| Silence ratio | ${formatRatio(report.silenceRatio)} |`,
2918
- `| Backpressure events | ${report.backpressureEvents} |`,
2919
- "",
2920
- "## Issues",
2921
- "",
2922
- renderIssuesTable(report.issues).trimEnd()
2923
- ];
2924
- return `${lines.join(`
2925
- `)}
2926
- `;
2927
- };
2928
- var renderMediaTransportMarkdown = (report, options = {}) => {
2929
- const title = options.title ?? `Media Transport: ${report.name}`;
2930
- const limit = options.redact?.truncateArraysAt ?? DEFAULT_TRUNCATE;
2931
- const events = report.events.slice(-limit);
2932
- const eventRows = events.length === 0 ? "- No transport events recorded." : ["| At | Kind | State | Buffered | Error |", "| --- | --- | --- | ---: | --- |"].concat(events.map((event) => `| ${event.at} | ${event.kind} | ${event.state} | ${event.bufferedFrames ?? ""} | ${escapeMarkdownCell(event.error ?? "")} |`)).join(`
2933
- `);
2934
- const lines = [
2935
- `# ${title}`,
2936
- "",
2937
- `Status: **${report.status}** \xB7 State: **${report.state}**`,
2938
- "",
2939
- "| Metric | Value |",
2940
- "| --- | ---: |",
2941
- `| Input frames | ${report.inputFrames} |`,
2942
- `| Output frames | ${report.outputFrames} |`,
2943
- `| Backpressure events | ${report.backpressureEvents} |`,
2944
- `| Connected | ${report.connected ? "yes" : "no"} |`,
2945
- `| Closed | ${report.closed ? "yes" : "no"} |`,
2946
- `| Failed | ${report.failed ? "yes" : "no"} |`,
2947
- "",
2948
- `## Last ${events.length} event(s)`,
2949
- "",
2950
- eventRows
2951
- ];
2952
- return `${lines.join(`
2953
- `)}
2954
- `;
2955
- };
2956
- var renderMediaProcessorGraphMarkdown = (report, options = {}) => {
2957
- const title = options.title ?? `Media Processor Graph: ${report.name}`;
2958
- const limit = options.redact?.truncateArraysAt ?? DEFAULT_TRUNCATE;
2959
- const nodeRows = report.nodes.map((node) => `| ${escapeMarkdownCell(node.name)} | ${node.kind} | ${node.status} | ${node.inputFrames} | ${node.emittedFrames} | ${node.droppedFrames} | ${node.errors.length} |`).join(`
2960
- `);
2961
- const edgeRows = report.edges.slice(0, limit).map((edge) => `| ${escapeMarkdownCell(edge.from)} | ${escapeMarkdownCell(edge.to)} | ${edge.status} | ${edge.emittedFrames} |`).join(`
2962
- `);
2963
- const issues = report.errors.map((event) => ({
2964
- code: event.kind,
2965
- message: event.error ?? `Processor graph ${event.kind} (state ${event.state}).`,
2966
- severity: "error"
2967
- }));
2968
- const lines = [
2969
- `# ${title}`,
2970
- "",
2971
- `Status: **${report.status}** \xB7 State: **${report.state}**`,
2972
- "",
2973
- "| Metric | Value |",
2974
- "| --- | ---: |",
2975
- `| Nodes | ${report.nodes.length} |`,
2976
- `| Input frames | ${report.inputFrames} |`,
2977
- `| Emitted frames | ${report.emittedFrames} |`,
2978
- `| Dropped frames | ${report.droppedFrames} |`,
2979
- `| Lifecycle events | ${report.lifecycleEvents.length} |`,
2980
- `| Edge events | ${report.edgeEvents.length} |`,
2981
- `| Backpressure events | ${report.backpressure.events.length} |`,
2982
- `| Timing max | ${formatOptionalMs(report.timing.maxNodeMs)} |`,
2983
- `| Timing average | ${formatOptionalMs(report.timing.averageNodeMs)} |`,
2984
- "",
2985
- "## Nodes",
2986
- "",
2987
- nodeRows ? `| Node | Kind | Status | In | Out | Dropped | Errors |
2988
- | --- | --- | --- | ---: | ---: | ---: | ---: |
2989
- ${nodeRows}` : "- No nodes.",
2990
- "",
2991
- `## Edges (showing up to ${limit})`,
2992
- "",
2993
- edgeRows ? `| From | To | Status | Frames |
2994
- | --- | --- | --- | ---: |
2995
- ${edgeRows}` : "- No edges.",
2996
- "",
2997
- "## Errors",
2998
- "",
2999
- renderIssuesTable(issues).trimEnd()
3000
- ];
3001
- return `${lines.join(`
3002
- `)}
3003
- `;
3004
- };
3005
- var truncateArrays = (value, limit, seen) => {
3006
- if (Array.isArray(value)) {
3007
- const head = value.slice(0, limit).map((entry) => truncateArrays(entry, limit, seen));
3008
- if (value.length > limit) {
3009
- return [...head, { truncated: value.length - limit }];
3010
- }
3011
- return head;
3012
- }
3013
- if (value && typeof value === "object") {
3014
- if (seen.has(value))
3015
- return value;
3016
- seen.add(value);
3017
- const next = {};
3018
- for (const [key, entry] of Object.entries(value)) {
3019
- next[key] = truncateArrays(entry, limit, seen);
3020
- }
3021
- return next;
3022
- }
3023
- return value;
3024
- };
3025
- var applyRedaction = (value, options, seen) => {
3026
- const mode = options.mode ?? "omit";
3027
- const maskValue = options.maskValue ?? "[redacted]";
3028
- const allow = new Set(options.metadataAllow ?? []);
3029
- const deny = new Set(options.metadataDeny ?? DEFAULT_METADATA_DENY);
3030
- const walk = (input) => {
3031
- if (Array.isArray(input)) {
3032
- return input.map((entry) => walk(entry));
3033
- }
3034
- if (input && typeof input === "object") {
3035
- if (seen.has(input))
3036
- return input;
3037
- seen.add(input);
3038
- const next = {};
3039
- for (const [key, entry] of Object.entries(input)) {
3040
- if (allow.has(key)) {
3041
- next[key] = entry;
3042
- continue;
3043
- }
3044
- if (deny.has(key)) {
3045
- if (mode === "mask")
3046
- next[key] = maskValue;
3047
- continue;
3048
- }
3049
- next[key] = walk(entry);
3050
- }
3051
- return next;
3052
- }
3053
- return input;
3054
- };
3055
- return walk(value);
3056
- };
3057
- var redactMediaReport = (report, options = {}) => {
3058
- const limit = options.truncateArraysAt ?? DEFAULT_TRUNCATE;
3059
- const truncated = truncateArrays(report, limit, new WeakSet);
3060
- return applyRedaction(truncated, options, new WeakSet);
3061
- };
3062
- var buildArtifactPair = (report, summary, markdown, options) => {
3063
- const jsonValue = options.redact ? redactMediaReport(report, options.redact) : report;
3064
- return {
3065
- json: JSON.stringify(jsonValue, null, 2),
3066
- jsonValue,
3067
- markdown,
3068
- summary
3069
- };
3070
- };
3071
- var buildMediaQualityArtifact = (report, options = {}) => buildArtifactPair(report, summarizeMediaQualityReport(report), renderMediaQualityMarkdown(report, options), options);
3072
- var buildMediaTransportArtifact = (report, options = {}) => buildArtifactPair(report, summarizeMediaTransportReport(report), renderMediaTransportMarkdown(report, options), options);
3073
- var buildMediaProcessorGraphArtifact = (report, options = {}) => buildArtifactPair(report, summarizeMediaProcessorGraphReport(report), renderMediaProcessorGraphMarkdown(report, options), options);
3074
- var writeMediaArtifact = async (input) => {
3075
- await mkdir(input.dir, { recursive: true });
3076
- const jsonPath = join(input.dir, `${input.slug}.json`);
3077
- const markdownPath = join(input.dir, `${input.slug}.md`);
3078
- await Promise.all([
3079
- writeFile(jsonPath, input.json, "utf8"),
3080
- writeFile(markdownPath, input.markdown, "utf8")
3081
- ]);
3082
- return {
3083
- jsonPath,
3084
- markdownPath,
3085
- summary: input.summary
3086
- };
3087
- };
3088
-
3089
2162
  // src/client/browserMedia.ts
2163
+ import {
2164
+ buildMediaWebRTCStatsReport,
2165
+ buildMediaWebRTCStreamContinuityReport,
2166
+ collectMediaWebRTCStats
2167
+ } from "@absolutejs/media";
3090
2168
  var DEFAULT_BROWSER_MEDIA_PATH = "/api/voice/browser-media";
3091
2169
  var DEFAULT_BROWSER_MEDIA_INTERVAL_MS = 5000;
3092
2170
  var resolvePeerConnection = async (options) => options.peerConnection ?? await options.getPeerConnection?.() ?? null;
@@ -8854,16 +7932,16 @@ var renderVoiceCallReviewHTML = (artifact) => {
8854
7932
  </html>`;
8855
7933
  };
8856
7934
  // src/testing/sessionBenchmark.ts
8857
- var average4 = (values) => values.length > 0 ? values.reduce((sum, value) => sum + value, 0) / values.length : 0;
7935
+ var average3 = (values) => values.length > 0 ? values.reduce((sum, value) => sum + value, 0) / values.length : 0;
8858
7936
  var normalizeTurnText = (value) => value.toLowerCase().replace(/[^\p{L}\p{N}\s']/gu, " ").replace(/\s+/g, " ").trim();
8859
7937
  var countPassedTurns = (turnResults) => turnResults.reduce((count, result) => count + (result.passes ? 1 : 0), 0);
8860
7938
  var calculateTurnPassRate = (turnResults) => turnResults.length > 0 ? countPassedTurns(turnResults) / turnResults.length : 0;
8861
7939
  var summarizeScenarioCosts = (turnResults) => {
8862
7940
  const costEstimates = turnResults.map((turn) => turn.quality?.cost).filter((value) => value !== undefined);
8863
7941
  return {
8864
- averageRelativeCostUnits: roundMetric5(average4(costEstimates.map((estimate) => estimate.estimatedRelativeCostUnits))),
8865
- fallbackReplayAudioMs: roundMetric5(average4(costEstimates.map((estimate) => estimate.fallbackReplayAudioMs)), 2),
8866
- primaryAudioMs: roundMetric5(average4(costEstimates.map((estimate) => estimate.primaryAudioMs)), 2)
7942
+ averageRelativeCostUnits: roundMetric5(average3(costEstimates.map((estimate) => estimate.estimatedRelativeCostUnits))),
7943
+ fallbackReplayAudioMs: roundMetric5(average3(costEstimates.map((estimate) => estimate.fallbackReplayAudioMs)), 2),
7944
+ primaryAudioMs: roundMetric5(average3(costEstimates.map((estimate) => estimate.primaryAudioMs)), 2)
8867
7945
  };
8868
7946
  };
8869
7947
  var roundMetric5 = (value, digits = 4) => {
@@ -9182,13 +8260,13 @@ var summarizeVoiceSessionBenchmark = (adapterId, scenarios) => {
9182
8260
  const turnAccuracies = scenarios.flatMap((scenario) => scenario.turnResults.map((turn) => turn.accuracy?.wordErrorRate).filter((value) => typeof value === "number"));
9183
8261
  return {
9184
8262
  adapterId,
9185
- averageElapsedMs: roundMetric5(average4(scenarios.map((scenario) => scenario.elapsedMs)), 2),
9186
- averageFallbackReplayAudioMs: roundMetric5(average4(scenarios.map((scenario) => scenario.fallbackReplayAudioMs)), 2),
9187
- averagePrimaryAudioMs: roundMetric5(average4(scenarios.map((scenario) => scenario.primaryAudioMs)), 2),
9188
- averageReconnectCount: roundMetric5(average4(scenarios.map((scenario) => scenario.reconnectCount))),
9189
- averageRelativeCostUnits: roundMetric5(average4(scenarios.map((scenario) => scenario.averageRelativeCostUnits))),
9190
- averageTurnPassRate: roundMetric5(average4(scenarios.map((scenario) => scenario.turnPassRate))),
9191
- averageWordErrorRate: roundMetric5(average4(turnAccuracies)),
8263
+ averageElapsedMs: roundMetric5(average3(scenarios.map((scenario) => scenario.elapsedMs)), 2),
8264
+ averageFallbackReplayAudioMs: roundMetric5(average3(scenarios.map((scenario) => scenario.fallbackReplayAudioMs)), 2),
8265
+ averagePrimaryAudioMs: roundMetric5(average3(scenarios.map((scenario) => scenario.primaryAudioMs)), 2),
8266
+ averageReconnectCount: roundMetric5(average3(scenarios.map((scenario) => scenario.reconnectCount))),
8267
+ averageRelativeCostUnits: roundMetric5(average3(scenarios.map((scenario) => scenario.averageRelativeCostUnits))),
8268
+ averageTurnPassRate: roundMetric5(average3(scenarios.map((scenario) => scenario.turnPassRate))),
8269
+ averageWordErrorRate: roundMetric5(average3(turnAccuracies)),
9192
8270
  duplicateTurnRate: roundMetric5(scenarios.length > 0 ? scenarios.filter((scenario) => scenario.duplicateTurnCount > 0).length / scenarios.length : 0),
9193
8271
  passCount,
9194
8272
  passRate: roundMetric5(scenarios.length > 0 ? passCount / scenarios.length : 0),
@@ -9214,13 +8292,13 @@ var summarizeVoiceSessionBenchmarkSeries = (input) => {
9214
8292
  const passCount = results.filter((scenario) => scenario.passes).length;
9215
8293
  const sample = results[0];
9216
8294
  return {
9217
- averageElapsedMs: roundMetric5(average4(results.map((scenario) => scenario.elapsedMs)), 2),
9218
- averageFallbackReplayAudioMs: roundMetric5(average4(results.map((scenario) => scenario.fallbackReplayAudioMs)), 2),
9219
- averagePrimaryAudioMs: roundMetric5(average4(results.map((scenario) => scenario.primaryAudioMs)), 2),
9220
- averageReconnectCount: roundMetric5(average4(results.map((scenario) => scenario.reconnectCount))),
9221
- averageRelativeCostUnits: roundMetric5(average4(results.map((scenario) => scenario.averageRelativeCostUnits))),
9222
- averageTurnPassRate: roundMetric5(average4(results.map((scenario) => scenario.turnPassRate))),
9223
- averageWordErrorRate: roundMetric5(average4(wordErrorRates)),
8295
+ averageElapsedMs: roundMetric5(average3(results.map((scenario) => scenario.elapsedMs)), 2),
8296
+ averageFallbackReplayAudioMs: roundMetric5(average3(results.map((scenario) => scenario.fallbackReplayAudioMs)), 2),
8297
+ averagePrimaryAudioMs: roundMetric5(average3(results.map((scenario) => scenario.primaryAudioMs)), 2),
8298
+ averageReconnectCount: roundMetric5(average3(results.map((scenario) => scenario.reconnectCount))),
8299
+ averageRelativeCostUnits: roundMetric5(average3(results.map((scenario) => scenario.averageRelativeCostUnits))),
8300
+ averageTurnPassRate: roundMetric5(average3(results.map((scenario) => scenario.turnPassRate))),
8301
+ averageWordErrorRate: roundMetric5(average3(wordErrorRates)),
9224
8302
  bestWordErrorRate: roundMetric5(wordErrorRates.length > 0 ? Math.min(...wordErrorRates) : 0),
9225
8303
  fixtureId,
9226
8304
  passCount,
@@ -9243,18 +8321,18 @@ var summarizeVoiceSessionBenchmarkSeries = (input) => {
9243
8321
  scenarios: scenarioAggregates,
9244
8322
  summary: {
9245
8323
  adapterId: input.adapterId,
9246
- averageElapsedMs: roundMetric5(average4(scenarioAggregates.map((scenario) => scenario.averageElapsedMs)), 2),
9247
- averageFallbackReplayAudioMs: roundMetric5(average4(scenarioAggregates.map((scenario) => scenario.averageFallbackReplayAudioMs)), 2),
9248
- averagePassRate: roundMetric5(average4(scenarioAggregates.map((scenario) => scenario.passRate))),
9249
- averagePrimaryAudioMs: roundMetric5(average4(scenarioAggregates.map((scenario) => scenario.averagePrimaryAudioMs)), 2),
9250
- averageReconnectCount: roundMetric5(average4(scenarioAggregates.map((scenario) => scenario.averageReconnectCount))),
9251
- averageRelativeCostUnits: roundMetric5(average4(scenarioAggregates.map((scenario) => scenario.averageRelativeCostUnits))),
9252
- averageTurnPassRate: roundMetric5(average4(scenarioAggregates.map((scenario) => scenario.averageTurnPassRate))),
9253
- averageWordErrorRate: roundMetric5(average4(scenarioAggregates.map((scenario) => scenario.averageWordErrorRate))),
8324
+ averageElapsedMs: roundMetric5(average3(scenarioAggregates.map((scenario) => scenario.averageElapsedMs)), 2),
8325
+ averageFallbackReplayAudioMs: roundMetric5(average3(scenarioAggregates.map((scenario) => scenario.averageFallbackReplayAudioMs)), 2),
8326
+ averagePassRate: roundMetric5(average3(scenarioAggregates.map((scenario) => scenario.passRate))),
8327
+ averagePrimaryAudioMs: roundMetric5(average3(scenarioAggregates.map((scenario) => scenario.averagePrimaryAudioMs)), 2),
8328
+ averageReconnectCount: roundMetric5(average3(scenarioAggregates.map((scenario) => scenario.averageReconnectCount))),
8329
+ averageRelativeCostUnits: roundMetric5(average3(scenarioAggregates.map((scenario) => scenario.averageRelativeCostUnits))),
8330
+ averageTurnPassRate: roundMetric5(average3(scenarioAggregates.map((scenario) => scenario.averageTurnPassRate))),
8331
+ averageWordErrorRate: roundMetric5(average3(scenarioAggregates.map((scenario) => scenario.averageWordErrorRate))),
9254
8332
  flakyScenarioCount: scenarioAggregates.filter((scenario) => scenario.passRate > 0 && scenario.passRate < 1).length,
9255
8333
  generatedRunCount: input.reports.length,
9256
- reconnectCoverageRate: roundMetric5(average4(reconnectCoverageRates)),
9257
- reconnectSuccessRate: roundMetric5(average4(reconnectRates)),
8334
+ reconnectCoverageRate: roundMetric5(average3(reconnectCoverageRates)),
8335
+ reconnectSuccessRate: roundMetric5(average3(reconnectRates)),
9258
8336
  scenarioCount: scenarioAggregates.length,
9259
8337
  stableScenarioCount: scenarioAggregates.filter((scenario) => scenario.passRate === 1).length,
9260
8338
  totalPassCount,
@@ -9298,6 +8376,11 @@ var runVoiceSessionBenchmarkSeries = async (input) => {
9298
8376
  };
9299
8377
  // src/operationsRecord.ts
9300
8378
  import { Elysia as Elysia4 } from "elysia";
8379
+ import {
8380
+ summarizeMediaProcessorGraphReport,
8381
+ summarizeMediaQualityReport,
8382
+ summarizeMediaTransportReport
8383
+ } from "@absolutejs/media";
9301
8384
 
9302
8385
  // src/audit.ts
9303
8386
  var includes = (filter, value) => {
@@ -10928,7 +10011,7 @@ import { Elysia as Elysia3 } from "elysia";
10928
10011
  var escapeHtml6 = (value) => value.replaceAll("&", "&amp;").replaceAll("<", "&lt;").replaceAll(">", "&gt;").replaceAll('"', "&quot;").replaceAll("'", "&#39;");
10929
10012
  var getString3 = (value) => typeof value === "string" && value.trim() ? value : undefined;
10930
10013
  var getNumber2 = (value) => typeof value === "number" && Number.isFinite(value) ? value : undefined;
10931
- var firstString2 = (payload, keys) => {
10014
+ var firstString = (payload, keys) => {
10932
10015
  for (const key of keys) {
10933
10016
  const value = getString3(payload[key]);
10934
10017
  if (value) {
@@ -10937,7 +10020,7 @@ var firstString2 = (payload, keys) => {
10937
10020
  }
10938
10021
  return;
10939
10022
  };
10940
- var firstNumber2 = (payload, keys) => {
10023
+ var firstNumber = (payload, keys) => {
10941
10024
  for (const key of keys) {
10942
10025
  const value = getNumber2(payload[key]);
10943
10026
  if (value !== undefined) {
@@ -10946,20 +10029,20 @@ var firstNumber2 = (payload, keys) => {
10946
10029
  }
10947
10030
  return;
10948
10031
  };
10949
- var eventProvider = (event) => firstString2(event.payload, [
10032
+ var eventProvider = (event) => firstString(event.payload, [
10950
10033
  "provider",
10951
10034
  "selectedProvider",
10952
10035
  "fallbackProvider",
10953
10036
  "variantId"
10954
10037
  ]);
10955
- var eventStatus = (event) => firstString2(event.payload, [
10038
+ var eventStatus = (event) => firstString(event.payload, [
10956
10039
  "providerStatus",
10957
10040
  "status",
10958
10041
  "disposition",
10959
10042
  "type",
10960
10043
  "reason"
10961
10044
  ]);
10962
- var eventElapsedMs = (event) => firstNumber2(event.payload, ["elapsedMs", "latencyMs", "durationMs"]);
10045
+ var eventElapsedMs = (event) => firstNumber(event.payload, ["elapsedMs", "latencyMs", "durationMs"]);
10963
10046
  var resolveSessionHref2 = (value, sessionId) => {
10964
10047
  if (value === false) {
10965
10048
  return;
@@ -11301,6 +10384,29 @@ var toTelephonyMediaEvent = (event) => {
11301
10384
  streamId
11302
10385
  };
11303
10386
  };
10387
+ var summarizeMediaPipelineForOperationsRecord = (report) => {
10388
+ const quality = summarizeMediaQualityReport(report.quality);
10389
+ const transport = report.transport ? summarizeMediaTransportReport(report.transport) : undefined;
10390
+ const processorGraph = report.processorGraph ? summarizeMediaProcessorGraphReport(report.processorGraph) : undefined;
10391
+ const calibrationCodes = report.calibration.issues.map((issue) => issue.code);
10392
+ const interruptionCodes = report.interruption.issues.map((issue) => issue.code);
10393
+ const issueCodes = Array.from(new Set([
10394
+ ...calibrationCodes,
10395
+ ...quality.issueCodes,
10396
+ ...interruptionCodes,
10397
+ ...processorGraph?.issueCodes ?? []
10398
+ ]));
10399
+ return {
10400
+ frames: report.frames,
10401
+ issueCodes,
10402
+ jitterMs: report.quality.jitterMs,
10403
+ processorGraphStatus: processorGraph?.status,
10404
+ qualityStatus: quality.status,
10405
+ status: report.status,
10406
+ surface: report.surface,
10407
+ transportStatus: transport?.status
10408
+ };
10409
+ };
11304
10410
  var summarizeTelephonyMedia = (events) => {
11305
10411
  const mediaEvents = events.map(toTelephonyMediaEvent).filter((event) => event !== undefined);
11306
10412
  const eventNames = mediaEvents.map((event) => event.event.toLowerCase());
@@ -11467,6 +10573,7 @@ var buildVoiceOperationsRecord = async (options) => {
11467
10573
  tasks,
11468
10574
  total: tasks.length
11469
10575
  } : undefined,
10576
+ mediaPipeline: options.mediaPipeline ? summarizeMediaPipelineForOperationsRecord(options.mediaPipeline) : undefined,
11470
10577
  telephonyMedia: summarizeTelephonyMedia(traceEvents),
11471
10578
  timeline: timelineSession?.events ?? [],
11472
10579
  tools: traceEvents.filter((event) => event.type === "agent.tool").map(toTool),
@@ -11684,15 +10791,18 @@ var buildVoiceFailureReplay = (record, options = {}) => {
11684
10791
  const recovery = step.status === "fallback" ? `recovered through ${step.fallbackProvider ?? step.selectedProvider ?? "fallback"}` : step.status === "degraded" ? `degraded to ${step.fallbackProvider ?? step.selectedProvider ?? "fallback"}` : "failed before recovery";
11685
10792
  return `${provider} ${recovery}${step.reason ? `: ${step.reason}` : ""}`;
11686
10793
  });
10794
+ const pipelineIssueCodes = record.mediaPipeline?.issueCodes ?? [];
11687
10795
  const mediaIssues = [
11688
10796
  ...mediaSteps.map((step) => step.issue).filter((issue) => typeof issue === "string"),
11689
10797
  record.telephonyMedia.total > 0 && record.telephonyMedia.inbound === 0 ? "Carrier stream has no inbound media evidence." : undefined,
11690
- record.telephonyMedia.total > 0 && record.telephonyMedia.outbound === 0 ? "Carrier stream has no outbound assistant media/control evidence." : undefined
10798
+ record.telephonyMedia.total > 0 && record.telephonyMedia.outbound === 0 ? "Carrier stream has no outbound assistant media/control evidence." : undefined,
10799
+ ...pipelineIssueCodes.map((code) => `Media pipeline issue: ${code}.`)
11691
10800
  ].filter((issue) => typeof issue === "string");
11692
10801
  const userHeard = [
11693
10802
  ...new Set(record.transcript.flatMap((turn) => turn.assistantReplies))
11694
10803
  ];
11695
- const status = record.providerDecisionSummary.errors > 0 || record.telephonyMedia.errors > 0 ? "failed" : record.providerDecisionSummary.degraded > 0 ? "degraded" : record.providerDecisionSummary.fallbacks > 0 || mediaIssues.length > 0 ? "recovered" : "healthy";
10804
+ const mediaPipelineStatus = record.mediaPipeline?.status;
10805
+ const status = record.providerDecisionSummary.errors > 0 || record.telephonyMedia.errors > 0 || mediaPipelineStatus === "fail" ? "failed" : record.providerDecisionSummary.degraded > 0 || mediaPipelineStatus === "warn" ? "degraded" : record.providerDecisionSummary.fallbacks > 0 || mediaIssues.length > 0 ? "recovered" : "healthy";
11696
10806
  const turns = record.transcript.map((turn) => ({
11697
10807
  ...turn,
11698
10808
  media: mediaSteps.filter((step) => record.traceEvents.some((event) => event.turnId === turn.id && event.type === "client.telephony_media" && event.at === step.at)),
@@ -11705,6 +10815,8 @@ var buildVoiceFailureReplay = (record, options = {}) => {
11705
10815
  clears: record.telephonyMedia.clears,
11706
10816
  errors: record.telephonyMedia.errors,
11707
10817
  issues: mediaIssues,
10818
+ pipelineIssueCodes,
10819
+ pipelineStatus: record.mediaPipeline?.status,
11708
10820
  steps: mediaSteps,
11709
10821
  total: record.telephonyMedia.total
11710
10822
  },
@@ -11761,6 +10873,8 @@ var renderVoiceFailureReplayMarkdown = (report) => {
11761
10873
  }).join(`
11762
10874
  `) : "- none recorded";
11763
10875
  const issues = report.summary.issues.length ? report.summary.issues.map((issue) => `- ${issue}`).join(`
10876
+ `) : "- none";
10877
+ const pipelineCodes = report.media.pipelineIssueCodes.length ? report.media.pipelineIssueCodes.map((code) => `- ${code}`).join(`
11764
10878
  `) : "- none";
11765
10879
  return [
11766
10880
  `# Voice Failure Replay: ${report.sessionId}`,
@@ -11778,6 +10892,9 @@ var renderVoiceFailureReplayMarkdown = (report) => {
11778
10892
  "## Media Path",
11779
10893
  mediaSteps,
11780
10894
  "",
10895
+ `## Media Pipeline (status: ${report.media.pipelineStatus ?? "n/a"})`,
10896
+ pipelineCodes,
10897
+ "",
11781
10898
  "## What The User Heard",
11782
10899
  heard
11783
10900
  ].join(`
@@ -12168,7 +11285,7 @@ var assertVoiceTelephonyWebhookNormalizationEvidence = (input = {}) => {
12168
11285
  return assertion;
12169
11286
  };
12170
11287
  var normalizeToken = (value) => typeof value === "string" ? value.trim().toLowerCase().replace(/\s+/g, "-").replace(/_+/g, "-") : undefined;
12171
- var firstString3 = (source, keys) => {
11288
+ var firstString2 = (source, keys) => {
12172
11289
  for (const key of keys) {
12173
11290
  const value = source[key];
12174
11291
  if (typeof value === "string" && value.trim()) {
@@ -12179,7 +11296,7 @@ var firstString3 = (source, keys) => {
12179
11296
  }
12180
11297
  }
12181
11298
  };
12182
- var firstNumber3 = (source, keys) => {
11299
+ var firstNumber2 = (source, keys) => {
12183
11300
  for (const key of keys) {
12184
11301
  const value = source[key];
12185
11302
  if (typeof value === "number" && Number.isFinite(value)) {
@@ -12544,8 +11661,8 @@ var verifyVoiceTelephonyWebhook = async (input) => {
12544
11661
  var durationMsFromSeconds = (value) => typeof value === "number" ? value * 1000 : undefined;
12545
11662
  var parseVoiceTelephonyWebhookEvent = (input) => {
12546
11663
  const payload = flattenPayload(input.body);
12547
- const provider = firstString3(payload, ["provider", "Provider"]) ?? input.provider;
12548
- const status = firstString3(payload, [
11664
+ const provider = firstString2(payload, ["provider", "Provider"]) ?? input.provider;
11665
+ const status = firstString2(payload, [
12549
11666
  "CallStatus",
12550
11667
  "call_status",
12551
11668
  "callStatus",
@@ -12555,7 +11672,7 @@ var parseVoiceTelephonyWebhookEvent = (input) => {
12555
11672
  "event_type",
12556
11673
  "type"
12557
11674
  ]);
12558
- const durationMs = firstNumber3(payload, ["durationMs", "duration_ms"]) ?? durationMsFromSeconds(firstNumber3(payload, [
11675
+ const durationMs = firstNumber2(payload, ["durationMs", "duration_ms"]) ?? durationMsFromSeconds(firstNumber2(payload, [
12559
11676
  "CallDuration",
12560
11677
  "call_duration",
12561
11678
  "callDuration",
@@ -12563,21 +11680,21 @@ var parseVoiceTelephonyWebhookEvent = (input) => {
12563
11680
  "dial_call_duration",
12564
11681
  "duration"
12565
11682
  ]));
12566
- const sipCode = firstNumber3(payload, [
11683
+ const sipCode = firstNumber2(payload, [
12567
11684
  "SipResponseCode",
12568
11685
  "sip_response_code",
12569
11686
  "sipCode",
12570
11687
  "sip_code",
12571
11688
  "hangupCauseCode"
12572
11689
  ]);
12573
- const from = firstString3(payload, ["From", "from", "caller_id", "callerId"]);
12574
- const to = firstString3(payload, [
11690
+ const from = firstString2(payload, ["From", "from", "caller_id", "callerId"]);
11691
+ const to = firstString2(payload, [
12575
11692
  "To",
12576
11693
  "to",
12577
11694
  "called_number",
12578
11695
  "calledNumber"
12579
11696
  ]);
12580
- const target = firstString3(payload, [
11697
+ const target = firstString2(payload, [
12581
11698
  "transferTarget",
12582
11699
  "TransferTarget",
12583
11700
  "target",
@@ -12585,7 +11702,7 @@ var parseVoiceTelephonyWebhookEvent = (input) => {
12585
11702
  "department"
12586
11703
  ]);
12587
11704
  return {
12588
- answeredBy: firstString3(payload, [
11705
+ answeredBy: firstString2(payload, [
12589
11706
  "AnsweredBy",
12590
11707
  "answered_by",
12591
11708
  "answeredBy",
@@ -12599,7 +11716,7 @@ var parseVoiceTelephonyWebhookEvent = (input) => {
12599
11716
  ...payload
12600
11717
  },
12601
11718
  provider,
12602
- reason: firstString3(payload, [
11719
+ reason: firstString2(payload, [
12603
11720
  "Reason",
12604
11721
  "reason",
12605
11722
  "HangupCause",
@@ -12615,7 +11732,7 @@ var parseVoiceTelephonyWebhookEvent = (input) => {
12615
11732
  var defaultSessionId = (input) => {
12616
11733
  const payload = flattenPayload(input.body);
12617
11734
  const metadataSessionId = input.event.metadata?.sessionId;
12618
- return firstString3(input.query, ["sessionId", "session_id"]) ?? firstString3(payload, [
11735
+ return firstString2(input.query, ["sessionId", "session_id"]) ?? firstString2(payload, [
12619
11736
  "sessionId",
12620
11737
  "session_id",
12621
11738
  "SessionId",
@@ -12630,7 +11747,7 @@ var defaultSessionId = (input) => {
12630
11747
  };
12631
11748
  var defaultIdempotencyKey = (input) => {
12632
11749
  const payload = flattenPayload(input.body);
12633
- const eventId = firstString3(payload, [
11750
+ const eventId = firstString2(payload, [
12634
11751
  "id",
12635
11752
  "event_id",
12636
11753
  "eventId",