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

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,
@@ -10928,7 +10006,7 @@ import { Elysia as Elysia3 } from "elysia";
10928
10006
  var escapeHtml6 = (value) => value.replaceAll("&", "&amp;").replaceAll("<", "&lt;").replaceAll(">", "&gt;").replaceAll('"', "&quot;").replaceAll("'", "&#39;");
10929
10007
  var getString3 = (value) => typeof value === "string" && value.trim() ? value : undefined;
10930
10008
  var getNumber2 = (value) => typeof value === "number" && Number.isFinite(value) ? value : undefined;
10931
- var firstString2 = (payload, keys) => {
10009
+ var firstString = (payload, keys) => {
10932
10010
  for (const key of keys) {
10933
10011
  const value = getString3(payload[key]);
10934
10012
  if (value) {
@@ -10937,7 +10015,7 @@ var firstString2 = (payload, keys) => {
10937
10015
  }
10938
10016
  return;
10939
10017
  };
10940
- var firstNumber2 = (payload, keys) => {
10018
+ var firstNumber = (payload, keys) => {
10941
10019
  for (const key of keys) {
10942
10020
  const value = getNumber2(payload[key]);
10943
10021
  if (value !== undefined) {
@@ -10946,20 +10024,20 @@ var firstNumber2 = (payload, keys) => {
10946
10024
  }
10947
10025
  return;
10948
10026
  };
10949
- var eventProvider = (event) => firstString2(event.payload, [
10027
+ var eventProvider = (event) => firstString(event.payload, [
10950
10028
  "provider",
10951
10029
  "selectedProvider",
10952
10030
  "fallbackProvider",
10953
10031
  "variantId"
10954
10032
  ]);
10955
- var eventStatus = (event) => firstString2(event.payload, [
10033
+ var eventStatus = (event) => firstString(event.payload, [
10956
10034
  "providerStatus",
10957
10035
  "status",
10958
10036
  "disposition",
10959
10037
  "type",
10960
10038
  "reason"
10961
10039
  ]);
10962
- var eventElapsedMs = (event) => firstNumber2(event.payload, ["elapsedMs", "latencyMs", "durationMs"]);
10040
+ var eventElapsedMs = (event) => firstNumber(event.payload, ["elapsedMs", "latencyMs", "durationMs"]);
10963
10041
  var resolveSessionHref2 = (value, sessionId) => {
10964
10042
  if (value === false) {
10965
10043
  return;
@@ -12168,7 +11246,7 @@ var assertVoiceTelephonyWebhookNormalizationEvidence = (input = {}) => {
12168
11246
  return assertion;
12169
11247
  };
12170
11248
  var normalizeToken = (value) => typeof value === "string" ? value.trim().toLowerCase().replace(/\s+/g, "-").replace(/_+/g, "-") : undefined;
12171
- var firstString3 = (source, keys) => {
11249
+ var firstString2 = (source, keys) => {
12172
11250
  for (const key of keys) {
12173
11251
  const value = source[key];
12174
11252
  if (typeof value === "string" && value.trim()) {
@@ -12179,7 +11257,7 @@ var firstString3 = (source, keys) => {
12179
11257
  }
12180
11258
  }
12181
11259
  };
12182
- var firstNumber3 = (source, keys) => {
11260
+ var firstNumber2 = (source, keys) => {
12183
11261
  for (const key of keys) {
12184
11262
  const value = source[key];
12185
11263
  if (typeof value === "number" && Number.isFinite(value)) {
@@ -12544,8 +11622,8 @@ var verifyVoiceTelephonyWebhook = async (input) => {
12544
11622
  var durationMsFromSeconds = (value) => typeof value === "number" ? value * 1000 : undefined;
12545
11623
  var parseVoiceTelephonyWebhookEvent = (input) => {
12546
11624
  const payload = flattenPayload(input.body);
12547
- const provider = firstString3(payload, ["provider", "Provider"]) ?? input.provider;
12548
- const status = firstString3(payload, [
11625
+ const provider = firstString2(payload, ["provider", "Provider"]) ?? input.provider;
11626
+ const status = firstString2(payload, [
12549
11627
  "CallStatus",
12550
11628
  "call_status",
12551
11629
  "callStatus",
@@ -12555,7 +11633,7 @@ var parseVoiceTelephonyWebhookEvent = (input) => {
12555
11633
  "event_type",
12556
11634
  "type"
12557
11635
  ]);
12558
- const durationMs = firstNumber3(payload, ["durationMs", "duration_ms"]) ?? durationMsFromSeconds(firstNumber3(payload, [
11636
+ const durationMs = firstNumber2(payload, ["durationMs", "duration_ms"]) ?? durationMsFromSeconds(firstNumber2(payload, [
12559
11637
  "CallDuration",
12560
11638
  "call_duration",
12561
11639
  "callDuration",
@@ -12563,21 +11641,21 @@ var parseVoiceTelephonyWebhookEvent = (input) => {
12563
11641
  "dial_call_duration",
12564
11642
  "duration"
12565
11643
  ]));
12566
- const sipCode = firstNumber3(payload, [
11644
+ const sipCode = firstNumber2(payload, [
12567
11645
  "SipResponseCode",
12568
11646
  "sip_response_code",
12569
11647
  "sipCode",
12570
11648
  "sip_code",
12571
11649
  "hangupCauseCode"
12572
11650
  ]);
12573
- const from = firstString3(payload, ["From", "from", "caller_id", "callerId"]);
12574
- const to = firstString3(payload, [
11651
+ const from = firstString2(payload, ["From", "from", "caller_id", "callerId"]);
11652
+ const to = firstString2(payload, [
12575
11653
  "To",
12576
11654
  "to",
12577
11655
  "called_number",
12578
11656
  "calledNumber"
12579
11657
  ]);
12580
- const target = firstString3(payload, [
11658
+ const target = firstString2(payload, [
12581
11659
  "transferTarget",
12582
11660
  "TransferTarget",
12583
11661
  "target",
@@ -12585,7 +11663,7 @@ var parseVoiceTelephonyWebhookEvent = (input) => {
12585
11663
  "department"
12586
11664
  ]);
12587
11665
  return {
12588
- answeredBy: firstString3(payload, [
11666
+ answeredBy: firstString2(payload, [
12589
11667
  "AnsweredBy",
12590
11668
  "answered_by",
12591
11669
  "answeredBy",
@@ -12599,7 +11677,7 @@ var parseVoiceTelephonyWebhookEvent = (input) => {
12599
11677
  ...payload
12600
11678
  },
12601
11679
  provider,
12602
- reason: firstString3(payload, [
11680
+ reason: firstString2(payload, [
12603
11681
  "Reason",
12604
11682
  "reason",
12605
11683
  "HangupCause",
@@ -12615,7 +11693,7 @@ var parseVoiceTelephonyWebhookEvent = (input) => {
12615
11693
  var defaultSessionId = (input) => {
12616
11694
  const payload = flattenPayload(input.body);
12617
11695
  const metadataSessionId = input.event.metadata?.sessionId;
12618
- return firstString3(input.query, ["sessionId", "session_id"]) ?? firstString3(payload, [
11696
+ return firstString2(input.query, ["sessionId", "session_id"]) ?? firstString2(payload, [
12619
11697
  "sessionId",
12620
11698
  "session_id",
12621
11699
  "SessionId",
@@ -12630,7 +11708,7 @@ var defaultSessionId = (input) => {
12630
11708
  };
12631
11709
  var defaultIdempotencyKey = (input) => {
12632
11710
  const payload = flattenPayload(input.body);
12633
- const eventId = firstString3(payload, [
11711
+ const eventId = firstString2(payload, [
12634
11712
  "id",
12635
11713
  "event_id",
12636
11714
  "eventId",