@absolutejs/voice 0.0.22-beta.327 → 0.0.22-beta.329
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/index.d.ts +1 -1
- package/dist/index.js +167 -15
- package/dist/operationsRecord.d.ts +26 -0
- package/dist/testing/index.js +67 -12
- package/package.json +1 -1
package/dist/index.d.ts
CHANGED
|
@@ -141,7 +141,7 @@ export type { VoiceProductionReadinessAction, VoiceProductionReadinessAuditOptio
|
|
|
141
141
|
export type { VoiceMonitorDefinition, VoiceMonitorEvaluation, VoiceMonitorEvaluationInput, VoiceMonitorIssue, VoiceMonitorIssueStatus, VoiceMonitorIssueStore, VoiceMonitorNotifier, VoiceMonitorNotifierDeliveryInput, VoiceMonitorNotifierDeliveryOptions, VoiceMonitorNotifierDeliveryReceipt, VoiceMonitorNotifierDeliveryReceiptStore, VoiceMonitorNotifierDeliveryReport, VoiceMonitorNotifierDeliveryResult, VoiceMonitorRoutesOptions, VoiceMonitorRun, VoiceMonitorRunOptions, VoiceMonitorRunReport, VoiceMonitorRunner, VoiceMonitorRunnerOptions, VoiceMonitorRunnerRoutesOptions, VoiceMonitorRunnerTickResult, VoiceMonitorSeverity, VoiceMonitorStatus, VoiceMonitorWebhookNotifierOptions } from './voiceMonitoring';
|
|
142
142
|
export type { VoiceReadinessProfileName, VoiceReadinessProfileOptions, VoiceReadinessProfileRecommendation, VoiceReadinessProfileRecommendationScore, VoiceReadinessProfileRoutesOptions } from './readinessProfiles';
|
|
143
143
|
export type { VoiceProviderStackChoice, VoiceProviderStackCapabilities, VoiceProviderStackCapabilityGap, VoiceProviderStackCapabilityGapInput, VoiceProviderStackCapabilityGapReport, VoiceProviderContractCheck, VoiceProviderContractCheckStatus, VoiceProviderContractDefinition, VoiceProviderContractMatrixAssertionInput, VoiceProviderContractMatrixAssertionReport, VoiceProviderContractMatrixHandlerOptions, VoiceProviderContractMatrixHTMLHandlerOptions, VoiceProviderContractMatrixInput, VoiceProviderContractMatrixPresetOptions, VoiceProviderContractMatrixReport, VoiceProviderContractMatrixRoutesOptions, VoiceProviderContractMatrixRow, VoiceProviderStackAssertionInput, VoiceProviderStackAssertionReport, VoiceProviderStackInput, VoiceProviderStackKind, VoiceProviderStackRecommendation } from './providerStackRecommendations';
|
|
144
|
-
export type { VoiceOperationsRecord, VoiceOperationsRecordAgentHandoff, VoiceOperationsRecordAuditSummary, VoiceOperationsRecordGuardrailAssertionInput, VoiceOperationsRecordGuardrailAssertionReport, VoiceOperationsRecordGuardrailDecision, VoiceOperationsRecordGuardrailFinding, VoiceOperationsRecordGuardrailSummary, VoiceOperationsRecordIntegrationEventSummary, VoiceOperationsRecordOptions, VoiceOperationsRecordOutcome, VoiceOperationsRecordProviderDecision, VoiceOperationsRecordProviderDecisionRecoveryStatus, VoiceOperationsRecordProviderDecisionSummary, VoiceOperationsRecordProviderRecoveryAssertionInput, VoiceOperationsRecordProviderRecoveryAssertionReport, VoiceOperationsRecordReviewSummary, VoiceOperationsRecordRoutesOptions, VoiceOperationsRecordStatus, VoiceOperationsRecordTaskSummary, VoiceOperationsRecordTranscriptTurn, VoiceOperationsRecordTool } from './operationsRecord';
|
|
144
|
+
export type { VoiceOperationsRecord, VoiceOperationsRecordAgentHandoff, VoiceOperationsRecordAuditSummary, VoiceOperationsRecordGuardrailAssertionInput, VoiceOperationsRecordGuardrailAssertionReport, VoiceOperationsRecordGuardrailDecision, VoiceOperationsRecordGuardrailFinding, VoiceOperationsRecordGuardrailSummary, VoiceOperationsRecordIntegrationEventSummary, VoiceOperationsRecordOptions, VoiceOperationsRecordOutcome, VoiceOperationsRecordProviderDecision, VoiceOperationsRecordProviderDecisionRecoveryStatus, VoiceOperationsRecordProviderDecisionSummary, VoiceOperationsRecordProviderRecoveryAssertionInput, VoiceOperationsRecordProviderRecoveryAssertionReport, VoiceOperationsRecordReviewSummary, VoiceOperationsRecordRoutesOptions, VoiceOperationsRecordStatus, VoiceOperationsRecordTaskSummary, VoiceOperationsRecordTelephonyMediaEvent, VoiceOperationsRecordTelephonyMediaSummary, VoiceOperationsRecordTranscriptTurn, VoiceOperationsRecordTool } from './operationsRecord';
|
|
145
145
|
export type { VoiceObservabilityExportArtifact, VoiceObservabilityExportArtifactChecksum, VoiceObservabilityExportArtifactFreshness, VoiceObservabilityExportArtifactIndex, VoiceObservabilityExportArtifactIndexItem, VoiceObservabilityExportArtifactKind, VoiceObservabilityExportDeliveryAssertionInput, VoiceObservabilityExportDeliveryAssertionReport, VoiceObservabilityExportDeliverySummary, VoiceObservabilityExportDeliveryDestination, VoiceObservabilityExportDeliveryDestinationResult, VoiceObservabilityExportDeliveryHistory, VoiceObservabilityExportDeliveryOptions, VoiceObservabilityExportDeliveryReceipt, VoiceObservabilityExportDeliveryReceiptStore, VoiceObservabilityExportDeliveryReport, VoiceObservabilityExportEnvelope, VoiceObservabilityExportIssue, VoiceObservabilityExportIssueCode, VoiceObservabilityExportOptions, VoiceObservabilityExportIngestedRecordKind, VoiceObservabilityExportRedactionSummary, VoiceObservabilityExportRecordValidationOptions, VoiceObservabilityExportReplayIssue, VoiceObservabilityExportReplayIssueCode, VoiceObservabilityExportReplayAssertionInput, VoiceObservabilityExportReplayAssertionReport, VoiceObservabilityExportReplayRecords, VoiceObservabilityExportReplayReport, VoiceObservabilityExportReplayRoutesOptions, VoiceObservabilityExportReplaySource, VoiceObservabilityExportReport, VoiceObservabilityExportRoutesOptions, VoiceObservabilityExportSchema, VoiceObservabilityExportStatus, VoiceObservabilityExportValidationIssue, VoiceObservabilityExportValidationResult } from './observabilityExport';
|
|
146
146
|
export type { VoiceOpsRecoveryFailedSession, VoiceOpsRecoveryInterventionSummary, VoiceOpsRecoveryIssue, VoiceOpsRecoveryIssueCode, VoiceOpsRecoveryLinks, VoiceOpsRecoveryProviderSummary, VoiceOpsRecoveryReport, VoiceOpsRecoveryReportOptions, VoiceOpsRecoveryRoutesOptions, VoiceOpsRecoveryStatus } from './opsRecovery';
|
|
147
147
|
export type { StoredVoiceIncidentBundleArtifact, VoiceIncidentBundle, VoiceIncidentBundleArtifactOptions, VoiceIncidentBundleFormat, VoiceIncidentBundleOptions, VoiceIncidentBundleRetentionOptions, VoiceIncidentBundleRetentionReport, VoiceIncidentBundleRoutesOptions, VoiceIncidentBundleStore, VoiceIncidentBundleStoreFilter, VoiceIncidentBundleSummary } from './incidentBundle';
|
package/dist/index.js
CHANGED
|
@@ -21722,12 +21722,35 @@ var createTwilioSocketAdapter = (socket, getState) => ({
|
|
|
21722
21722
|
}
|
|
21723
21723
|
if (message.type === "audio") {
|
|
21724
21724
|
const payload = transcodePCMToTwilioOutboundPayload(Uint8Array.from(Buffer4.from(message.chunkBase64, "base64")), message.format);
|
|
21725
|
+
const outboundMessage = {
|
|
21726
|
+
event: "media",
|
|
21727
|
+
media: {
|
|
21728
|
+
payload,
|
|
21729
|
+
track: "outbound"
|
|
21730
|
+
},
|
|
21731
|
+
streamSid: state.streamSid
|
|
21732
|
+
};
|
|
21725
21733
|
state.hasOutboundAudioSinceLastInbound = true;
|
|
21726
21734
|
state.reviewRecorder?.recordTwilioOutbound({
|
|
21727
21735
|
bytes: payload.length,
|
|
21728
21736
|
event: "media",
|
|
21729
21737
|
track: "outbound"
|
|
21730
21738
|
});
|
|
21739
|
+
await state.trace?.append({
|
|
21740
|
+
at: Date.now(),
|
|
21741
|
+
payload: {
|
|
21742
|
+
audioBytes: Buffer4.from(payload, "base64").byteLength,
|
|
21743
|
+
callSid: state.callSid ?? undefined,
|
|
21744
|
+
carrier: state.carrier,
|
|
21745
|
+
direction: "outbound",
|
|
21746
|
+
envelope: outboundMessage,
|
|
21747
|
+
event: "media",
|
|
21748
|
+
streamId: state.streamSid
|
|
21749
|
+
},
|
|
21750
|
+
scenarioId: state.scenarioId ?? undefined,
|
|
21751
|
+
sessionId: state.sessionId ?? state.streamSid,
|
|
21752
|
+
type: "client.telephony_media"
|
|
21753
|
+
});
|
|
21731
21754
|
await Promise.resolve(socket.send(JSON.stringify({
|
|
21732
21755
|
event: "media",
|
|
21733
21756
|
media: {
|
|
@@ -21738,17 +21761,32 @@ var createTwilioSocketAdapter = (socket, getState) => ({
|
|
|
21738
21761
|
return;
|
|
21739
21762
|
}
|
|
21740
21763
|
if (message.type === "assistant" && message.turnId) {
|
|
21741
|
-
|
|
21742
|
-
event: "mark",
|
|
21743
|
-
name: `assistant:${message.turnId}`
|
|
21744
|
-
});
|
|
21745
|
-
await Promise.resolve(socket.send(JSON.stringify({
|
|
21764
|
+
const outboundMessage = {
|
|
21746
21765
|
event: "mark",
|
|
21747
21766
|
mark: {
|
|
21748
21767
|
name: `assistant:${message.turnId}`
|
|
21749
21768
|
},
|
|
21750
21769
|
streamSid: state.streamSid
|
|
21751
|
-
}
|
|
21770
|
+
};
|
|
21771
|
+
state.reviewRecorder?.recordTwilioOutbound({
|
|
21772
|
+
event: "mark",
|
|
21773
|
+
name: `assistant:${message.turnId}`
|
|
21774
|
+
});
|
|
21775
|
+
await state.trace?.append({
|
|
21776
|
+
at: Date.now(),
|
|
21777
|
+
payload: {
|
|
21778
|
+
callSid: state.callSid ?? undefined,
|
|
21779
|
+
carrier: state.carrier,
|
|
21780
|
+
direction: "outbound",
|
|
21781
|
+
envelope: outboundMessage,
|
|
21782
|
+
event: "mark",
|
|
21783
|
+
streamId: state.streamSid
|
|
21784
|
+
},
|
|
21785
|
+
scenarioId: state.scenarioId ?? undefined,
|
|
21786
|
+
sessionId: state.sessionId ?? state.streamSid,
|
|
21787
|
+
type: "client.telephony_media"
|
|
21788
|
+
});
|
|
21789
|
+
await Promise.resolve(socket.send(JSON.stringify(outboundMessage)));
|
|
21752
21790
|
}
|
|
21753
21791
|
}
|
|
21754
21792
|
});
|
|
@@ -21771,6 +21809,7 @@ var createTwilioMediaStreamBridge = (socket, options) => {
|
|
|
21771
21809
|
};
|
|
21772
21810
|
const bridgeState = {
|
|
21773
21811
|
callSid: null,
|
|
21812
|
+
carrier: options.telephonyMediaCarrier ?? "twilio",
|
|
21774
21813
|
hasOutboundAudioSinceLastInbound: false,
|
|
21775
21814
|
onVoiceMessage: options.onVoiceMessage,
|
|
21776
21815
|
reviewRecorder: options.review ? createVoiceCallReviewRecorder({
|
|
@@ -21790,11 +21829,12 @@ var createTwilioMediaStreamBridge = (socket, options) => {
|
|
|
21790
21829
|
}) : undefined,
|
|
21791
21830
|
scenarioId: options.scenarioId ?? null,
|
|
21792
21831
|
sessionId: options.sessionId ?? null,
|
|
21793
|
-
streamSid: null
|
|
21832
|
+
streamSid: null,
|
|
21833
|
+
trace: options.trace
|
|
21794
21834
|
};
|
|
21795
21835
|
let sessionHandle = null;
|
|
21796
21836
|
let reviewArtifactDelivered = false;
|
|
21797
|
-
const telephonyMediaCarrier =
|
|
21837
|
+
const telephonyMediaCarrier = bridgeState.carrier;
|
|
21798
21838
|
const appendTelephonyMediaTrace = async (message, override) => {
|
|
21799
21839
|
const trace = options.trace;
|
|
21800
21840
|
const sessionId = override?.sessionId ?? bridgeState.sessionId ?? (message.event === "start" ? message.start.customParameters?.sessionId : undefined) ?? (message.event === "start" ? message.start.streamSid : "telephony-media");
|
|
@@ -21928,13 +21968,28 @@ var createTwilioMediaStreamBridge = (socket, options) => {
|
|
|
21928
21968
|
track: message.media.track
|
|
21929
21969
|
});
|
|
21930
21970
|
if (options.clearOnInboundMedia !== false && bridgeState.hasOutboundAudioSinceLastInbound && bridgeState.streamSid) {
|
|
21971
|
+
const outboundMessage = {
|
|
21972
|
+
event: "clear",
|
|
21973
|
+
streamSid: bridgeState.streamSid
|
|
21974
|
+
};
|
|
21931
21975
|
bridgeState.reviewRecorder?.recordTwilioOutbound({
|
|
21932
21976
|
event: "clear"
|
|
21933
21977
|
});
|
|
21934
|
-
await
|
|
21935
|
-
|
|
21936
|
-
|
|
21937
|
-
|
|
21978
|
+
await options.trace?.append({
|
|
21979
|
+
at: Date.now(),
|
|
21980
|
+
payload: {
|
|
21981
|
+
callSid: bridgeState.callSid ?? undefined,
|
|
21982
|
+
carrier: telephonyMediaCarrier,
|
|
21983
|
+
direction: "outbound",
|
|
21984
|
+
envelope: outboundMessage,
|
|
21985
|
+
event: "clear",
|
|
21986
|
+
streamId: bridgeState.streamSid
|
|
21987
|
+
},
|
|
21988
|
+
scenarioId: bridgeState.scenarioId ?? undefined,
|
|
21989
|
+
sessionId: bridgeState.sessionId ?? bridgeState.streamSid,
|
|
21990
|
+
type: "client.telephony_media"
|
|
21991
|
+
});
|
|
21992
|
+
await Promise.resolve(socket.send(JSON.stringify(outboundMessage)));
|
|
21938
21993
|
}
|
|
21939
21994
|
bridgeState.hasOutboundAudioSinceLastInbound = false;
|
|
21940
21995
|
await appendTelephonyMediaTrace(message, {
|
|
@@ -27765,6 +27820,7 @@ var createVoiceTraceTimelineRoutes = (options) => {
|
|
|
27765
27820
|
var getString17 = (value) => typeof value === "string" ? value : undefined;
|
|
27766
27821
|
var getNumber10 = (value) => typeof value === "number" && Number.isFinite(value) ? value : undefined;
|
|
27767
27822
|
var getBoolean3 = (value) => typeof value === "boolean" ? value : undefined;
|
|
27823
|
+
var getRecord = (value) => value && typeof value === "object" && !Array.isArray(value) ? value : undefined;
|
|
27768
27824
|
var countOutcome = (events, outcome) => events.filter((event) => event.outcome === outcome).length;
|
|
27769
27825
|
var matchesSessionScopedId = (id, sessionId) => id === sessionId || id.startsWith(`${sessionId}:`);
|
|
27770
27826
|
var hasPayloadValue = (payload, key, values) => {
|
|
@@ -27823,6 +27879,60 @@ var toGuardrailDecision = (event) => ({
|
|
|
27823
27879
|
toolName: getString17(event.payload.toolName),
|
|
27824
27880
|
turnId: event.turnId
|
|
27825
27881
|
});
|
|
27882
|
+
var decodeBase64Bytes = (value) => {
|
|
27883
|
+
if (typeof value !== "string") {
|
|
27884
|
+
return 0;
|
|
27885
|
+
}
|
|
27886
|
+
try {
|
|
27887
|
+
return Buffer.from(value, "base64").byteLength;
|
|
27888
|
+
} catch {
|
|
27889
|
+
return 0;
|
|
27890
|
+
}
|
|
27891
|
+
};
|
|
27892
|
+
var toTelephonyMediaEvent = (event) => {
|
|
27893
|
+
if (event.type !== "client.telephony_media") {
|
|
27894
|
+
return;
|
|
27895
|
+
}
|
|
27896
|
+
const envelope = getRecord(event.payload.envelope);
|
|
27897
|
+
const start = getRecord(envelope?.start);
|
|
27898
|
+
const media = getRecord(envelope?.media);
|
|
27899
|
+
const stop = getRecord(envelope?.stop);
|
|
27900
|
+
const error = getRecord(envelope?.error);
|
|
27901
|
+
const eventName = getString17(event.payload.event) ?? getString17(envelope?.event) ?? getString17(event.payload.kind) ?? "unknown";
|
|
27902
|
+
const streamId = getString17(event.payload.streamId) ?? getString17(envelope?.streamSid) ?? getString17(start?.streamSid);
|
|
27903
|
+
const callSid = getString17(event.payload.callSid) ?? getString17(start?.callSid) ?? getString17(stop?.callSid);
|
|
27904
|
+
const sequenceNumber = getString17(media?.sequenceNumber) ?? getString17(envelope?.sequenceNumber);
|
|
27905
|
+
const direction = getString17(media?.track) ?? getString17(media?.direction) ?? getString17(event.payload.direction);
|
|
27906
|
+
return {
|
|
27907
|
+
at: event.at,
|
|
27908
|
+
audioBytes: getNumber10(event.payload.audioBytes) ?? getNumber10(media?.audioBytes) ?? decodeBase64Bytes(media?.payload),
|
|
27909
|
+
callSid,
|
|
27910
|
+
carrier: getString17(event.payload.carrier),
|
|
27911
|
+
direction,
|
|
27912
|
+
event: eventName,
|
|
27913
|
+
sequenceNumber,
|
|
27914
|
+
streamId
|
|
27915
|
+
};
|
|
27916
|
+
};
|
|
27917
|
+
var summarizeTelephonyMedia = (events) => {
|
|
27918
|
+
const mediaEvents = events.map(toTelephonyMediaEvent).filter((event) => event !== undefined);
|
|
27919
|
+
const eventNames = mediaEvents.map((event) => event.event.toLowerCase());
|
|
27920
|
+
return {
|
|
27921
|
+
audioBytes: mediaEvents.reduce((total, event) => total + event.audioBytes, 0),
|
|
27922
|
+
carriers: uniqueSorted8(mediaEvents.map((event) => event.carrier)),
|
|
27923
|
+
clears: eventNames.filter((event) => event === "clear").length,
|
|
27924
|
+
errors: eventNames.filter((event) => event === "error").length,
|
|
27925
|
+
events: mediaEvents,
|
|
27926
|
+
inbound: mediaEvents.filter((event) => event.direction === "inbound").length,
|
|
27927
|
+
marks: eventNames.filter((event) => event === "mark").length,
|
|
27928
|
+
media: eventNames.filter((event) => event === "media").length,
|
|
27929
|
+
outbound: mediaEvents.filter((event) => event.direction === "outbound").length,
|
|
27930
|
+
starts: eventNames.filter((event) => event === "start").length,
|
|
27931
|
+
stops: eventNames.filter((event) => event === "stop").length,
|
|
27932
|
+
streamIds: uniqueSorted8(mediaEvents.map((event) => event.streamId)),
|
|
27933
|
+
total: mediaEvents.length
|
|
27934
|
+
};
|
|
27935
|
+
};
|
|
27826
27936
|
var summarizeGuardrails = (events) => {
|
|
27827
27937
|
const decisions = events.filter((event) => event.type === "assistant.guardrail").map(toGuardrailDecision);
|
|
27828
27938
|
const isBlocked = (decision) => decision.allowed === false || decision.status === "fail";
|
|
@@ -27970,6 +28080,7 @@ var buildVoiceOperationsRecord = async (options) => {
|
|
|
27970
28080
|
tasks,
|
|
27971
28081
|
total: tasks.length
|
|
27972
28082
|
} : undefined,
|
|
28083
|
+
telephonyMedia: summarizeTelephonyMedia(traceEvents),
|
|
27973
28084
|
timeline: timelineSession?.events ?? [],
|
|
27974
28085
|
tools: traceEvents.filter((event) => event.type === "agent.tool").map(toTool),
|
|
27975
28086
|
traceEvents,
|
|
@@ -28174,6 +28285,31 @@ var renderVoiceOperationsRecordIncidentMarkdown = (record) => {
|
|
|
28174
28285
|
`degraded=${String(providerDecisionSummary.degraded)}`,
|
|
28175
28286
|
`errors=${String(providerDecisionSummary.errors)}`
|
|
28176
28287
|
].join("; ");
|
|
28288
|
+
const telephonyMediaLine = [
|
|
28289
|
+
`events=${String(record.telephonyMedia.total)}`,
|
|
28290
|
+
`starts=${String(record.telephonyMedia.starts)}`,
|
|
28291
|
+
`media=${String(record.telephonyMedia.media)}`,
|
|
28292
|
+
`inbound=${String(record.telephonyMedia.inbound)}`,
|
|
28293
|
+
`outbound=${String(record.telephonyMedia.outbound)}`,
|
|
28294
|
+
`marks=${String(record.telephonyMedia.marks)}`,
|
|
28295
|
+
`clears=${String(record.telephonyMedia.clears)}`,
|
|
28296
|
+
`stops=${String(record.telephonyMedia.stops)}`,
|
|
28297
|
+
`errors=${String(record.telephonyMedia.errors)}`,
|
|
28298
|
+
`audioBytes=${String(record.telephonyMedia.audioBytes)}`,
|
|
28299
|
+
`carriers=${record.telephonyMedia.carriers.join(", ") || "none"}`,
|
|
28300
|
+
`streams=${record.telephonyMedia.streamIds.join(", ") || "none"}`
|
|
28301
|
+
].join("; ");
|
|
28302
|
+
const telephonyMediaLines = record.telephonyMedia.events.length ? record.telephonyMedia.events.slice(0, 12).map((event) => {
|
|
28303
|
+
const parts = [
|
|
28304
|
+
event.carrier ? `carrier=${event.carrier}` : undefined,
|
|
28305
|
+
event.streamId ? `stream=${event.streamId}` : undefined,
|
|
28306
|
+
event.callSid ? `call=${event.callSid}` : undefined,
|
|
28307
|
+
event.direction ? `direction=${event.direction}` : undefined,
|
|
28308
|
+
event.sequenceNumber ? `seq=${event.sequenceNumber}` : undefined,
|
|
28309
|
+
`audioBytes=${String(event.audioBytes)}`
|
|
28310
|
+
].filter((part) => typeof part === "string");
|
|
28311
|
+
return `- ${event.event}: ${parts.join("; ")}`;
|
|
28312
|
+
}) : ["- none recorded"];
|
|
28177
28313
|
return [
|
|
28178
28314
|
`# Voice incident handoff: ${record.sessionId}`,
|
|
28179
28315
|
"",
|
|
@@ -28187,11 +28323,16 @@ var renderVoiceOperationsRecordIncidentMarkdown = (record) => {
|
|
|
28187
28323
|
`- Top errors: ${topErrors.join("; ") || "none"}`,
|
|
28188
28324
|
`- Guardrails: ${String(record.guardrails.blocked)} blocked / ${String(record.guardrails.warned)} warned / ${String(record.guardrails.total)} decisions`,
|
|
28189
28325
|
`- Provider recovery: ${providerRecoveryLine}`,
|
|
28326
|
+
`- Telephony media: ${telephonyMediaLine}`,
|
|
28190
28327
|
"",
|
|
28191
28328
|
"## Provider decisions",
|
|
28192
28329
|
"",
|
|
28193
28330
|
...providerDecisionLines,
|
|
28194
28331
|
"",
|
|
28332
|
+
"## Telephony media",
|
|
28333
|
+
"",
|
|
28334
|
+
...telephonyMediaLines,
|
|
28335
|
+
"",
|
|
28195
28336
|
renderVoiceOperationsRecordGuardrailMarkdown(record),
|
|
28196
28337
|
"",
|
|
28197
28338
|
"## Next checks",
|
|
@@ -28234,6 +28375,17 @@ var renderVoiceOperationsRecordHTML = (record, options = {}) => {
|
|
|
28234
28375
|
const findings = decision.findings.map((finding) => finding.label ?? finding.ruleId ?? finding.action).filter((value) => typeof value === "string").join(", ") || "none";
|
|
28235
28376
|
return `<li><strong>assistant.guardrail ${escapeHtml48(decision.stage ?? "unknown")}</strong> <span>${escapeHtml48(decision.status ?? "")}</span><p>Allowed: ${escapeHtml48(String(decision.allowed ?? "unknown"))} \xB7 Proof: ${escapeHtml48(decision.proof ?? "runtime")}${decision.turnId ? ` \xB7 Turn: ${escapeHtml48(decision.turnId)}` : ""}</p><p>${escapeHtml48(findings)}</p></li>`;
|
|
28236
28377
|
}).join("") : "<li>No assistant.guardrail events recorded.</li>";
|
|
28378
|
+
const telephonyMedia = record.telephonyMedia.events.length ? record.telephonyMedia.events.slice(0, 50).map((event) => {
|
|
28379
|
+
const details = [
|
|
28380
|
+
event.carrier ? `Carrier: ${event.carrier}` : undefined,
|
|
28381
|
+
event.streamId ? `Stream: ${event.streamId}` : undefined,
|
|
28382
|
+
event.callSid ? `Call: ${event.callSid}` : undefined,
|
|
28383
|
+
event.direction ? `Direction: ${event.direction}` : undefined,
|
|
28384
|
+
event.sequenceNumber ? `Seq: ${event.sequenceNumber}` : undefined,
|
|
28385
|
+
`Audio bytes: ${String(event.audioBytes)}`
|
|
28386
|
+
].filter((detail) => typeof detail === "string");
|
|
28387
|
+
return `<li><strong>${escapeHtml48(event.event)}</strong> <span>${escapeHtml48(new Date(event.at).toLocaleString())}</span><p>${escapeHtml48(details.join(" \xB7 "))}</p></li>`;
|
|
28388
|
+
}).join("") : "<li>No telephony media trace events recorded.</li>";
|
|
28237
28389
|
const snippet = escapeHtml48(`app.use(
|
|
28238
28390
|
createVoiceOperationsRecordRoutes({
|
|
28239
28391
|
audit: auditStore,
|
|
@@ -28250,7 +28402,7 @@ var renderVoiceOperationsRecordHTML = (record, options = {}) => {
|
|
|
28250
28402
|
);`);
|
|
28251
28403
|
const incidentMarkdown = escapeHtml48(renderVoiceOperationsRecordIncidentMarkdown(record));
|
|
28252
28404
|
const incidentLink = options.incidentHref ? `<a href="${escapeHtml48(options.incidentHref)}">Download incident.md</a>` : "";
|
|
28253
|
-
return `<!doctype html><html lang="en"><head><meta charset="utf-8" /><meta name="viewport" content="width=device-width, initial-scale=1" /><title>${escapeHtml48(options.title ?? "Voice Operations Record")}</title><style>body{background:#101417;color:#f9f4e8;font-family:ui-sans-serif,system-ui,sans-serif;margin:0}main{margin:auto;max-width:1120px;padding:32px}.eyebrow{color:#fbbf24;font-size:.8rem;font-weight:900;letter-spacing:.14em;text-transform:uppercase}h1{font-size:clamp(2.4rem,6vw,4.8rem);line-height:.9;margin:.2rem 0 1rem}.status{border:1px solid #475569;border-radius:999px;display:inline-flex;padding:8px 12px}.healthy{color:#86efac}.warning{color:#fbbf24}.failed,.error{color:#fca5a5}.grid{display:grid;gap:14px;grid-template-columns:repeat(auto-fit,minmax(180px,1fr));margin:20px 0}.card,.primitive{background:#182025;border:1px solid #2d3a43;border-radius:20px;padding:16px}.card span,.muted,.label{color:#a9b4bd}.label{display:block;font-size:.72rem;font-weight:900;letter-spacing:.12em;text-transform:uppercase}.card strong{display:block;font-size:2rem}section{margin-top:28px}article{display:grid;gap:8px}ul{display:grid;gap:10px;list-style:none;padding:0}li{background:#182025;border:1px solid #2d3a43;border-radius:16px;padding:14px}pre{background:#080d10;border:1px solid #2d3a43;border-radius:16px;color:#dbeafe;overflow:auto;padding:14px}.hero-actions{display:flex;flex-wrap:wrap;gap:10px;margin-top:16px}.hero-actions a{background:#fbbf24;border-radius:999px;color:#111827;font-weight:900;padding:10px 14px;text-decoration:none}.two-column{display:grid;gap:18px;grid-template-columns:minmax(0,1.15fr) minmax(280px,.85fr)}@media(max-width:860px){main{padding:20px}.two-column{grid-template-columns:1fr}}</style></head><body><main><p class="eyebrow">Call log replacement</p><h1>${escapeHtml48(options.title ?? "Voice Operations Record")}</h1><p class="status ${escapeHtml48(record.status)}">${escapeHtml48(record.status)}</p><div class="hero-actions"><a href="#transcript">Transcript</a><a href="#provider-decisions">Provider decisions</a><a href="#guardrails">Guardrails</a><a href="#incident-handoff">Incident handoff</a>${incidentLink}</div><section class="grid"><div class="card"><span>Events</span><strong>${String(record.summary.eventCount)}</strong></div><div class="card"><span>Turns</span><strong>${String(record.summary.turnCount)}</strong></div><div class="card"><span>Errors</span><strong>${String(record.summary.errorCount)}</strong></div><div class="card"><span>Duration</span><strong>${formatMs5(record.summary.callDurationMs)}</strong></div><div class="card"><span>Provider recovery</span><strong>${escapeHtml48(providerDecisionSummary.recoveryStatus)}</strong><span>${String(providerDecisionSummary.fallbacks)} fallback / ${String(providerDecisionSummary.degraded)} degraded / ${String(providerDecisionSummary.errors)} errors</span></div><div class="card"><span>Guardrails</span><strong>${String(record.guardrails.blocked)}</strong></div><div class="card"><span>Audit</span><strong>${String(record.audit?.total ?? 0)}</strong></div><div class="card"><span>Reviews</span><strong>${String(record.reviews?.total ?? 0)}</strong></div><div class="card"><span>Tasks</span><strong>${String(record.tasks?.total ?? 0)}</strong></div><div class="card"><span>Integrations</span><strong>${String(record.integrationEvents?.total ?? 0)}</strong></div></section><section class="two-column"><div><h2 id="transcript">Transcript</h2><ul>${transcript}</ul></div><div><h2 id="provider-decisions">Provider Decisions</h2><ul>${providerDecisions}</ul></div></section><section id="guardrails"><h2>Guardrail Evidence</h2><p class="muted">Live <code>assistant.guardrail</code> decisions attached to this session.</p><ul>${guardrails}</ul></section><section id="incident-handoff"><h2>Copyable Incident Handoff</h2><p class="muted">Paste this into Slack, Linear, Zendesk, or an incident review. ${incidentLink}</p><pre><code>${incidentMarkdown}</code></pre></section><section class="primitive"><p class="eyebrow">Copy into your app</p><h2><code>createVoiceOperationsRecordRoutes(...)</code> gives every call one debuggable object</h2><p class="muted">Use this as the support/debug payload across traces, provider routing, tools, handoffs, guardrails, audit, latency, replay, reviews, tasks, and webhook delivery.</p><pre><code>${snippet}</code></pre></section><section><h2>Provider Summary</h2><div class="grid">${providers}</div></section><section><h2>Handoffs</h2><ul>${handoffs}</ul></section><section><h2>Tools</h2><ul>${tools}</ul></section><section><h2>Reviews</h2><ul>${reviews}</ul></section><section><h2>Tasks</h2><ul>${tasks}</ul></section><section><h2>Integration Events</h2><ul>${integrationEvents}</ul></section></main></body></html>`;
|
|
28405
|
+
return `<!doctype html><html lang="en"><head><meta charset="utf-8" /><meta name="viewport" content="width=device-width, initial-scale=1" /><title>${escapeHtml48(options.title ?? "Voice Operations Record")}</title><style>body{background:#101417;color:#f9f4e8;font-family:ui-sans-serif,system-ui,sans-serif;margin:0}main{margin:auto;max-width:1120px;padding:32px}.eyebrow{color:#fbbf24;font-size:.8rem;font-weight:900;letter-spacing:.14em;text-transform:uppercase}h1{font-size:clamp(2.4rem,6vw,4.8rem);line-height:.9;margin:.2rem 0 1rem}.status{border:1px solid #475569;border-radius:999px;display:inline-flex;padding:8px 12px}.healthy{color:#86efac}.warning{color:#fbbf24}.failed,.error{color:#fca5a5}.grid{display:grid;gap:14px;grid-template-columns:repeat(auto-fit,minmax(180px,1fr));margin:20px 0}.card,.primitive{background:#182025;border:1px solid #2d3a43;border-radius:20px;padding:16px}.card span,.muted,.label{color:#a9b4bd}.label{display:block;font-size:.72rem;font-weight:900;letter-spacing:.12em;text-transform:uppercase}.card strong{display:block;font-size:2rem}section{margin-top:28px}article{display:grid;gap:8px}ul{display:grid;gap:10px;list-style:none;padding:0}li{background:#182025;border:1px solid #2d3a43;border-radius:16px;padding:14px}pre{background:#080d10;border:1px solid #2d3a43;border-radius:16px;color:#dbeafe;overflow:auto;padding:14px}.hero-actions{display:flex;flex-wrap:wrap;gap:10px;margin-top:16px}.hero-actions a{background:#fbbf24;border-radius:999px;color:#111827;font-weight:900;padding:10px 14px;text-decoration:none}.two-column{display:grid;gap:18px;grid-template-columns:minmax(0,1.15fr) minmax(280px,.85fr)}@media(max-width:860px){main{padding:20px}.two-column{grid-template-columns:1fr}}</style></head><body><main><p class="eyebrow">Call log replacement</p><h1>${escapeHtml48(options.title ?? "Voice Operations Record")}</h1><p class="status ${escapeHtml48(record.status)}">${escapeHtml48(record.status)}</p><div class="hero-actions"><a href="#transcript">Transcript</a><a href="#provider-decisions">Provider decisions</a><a href="#telephony-media">Telephony media</a><a href="#guardrails">Guardrails</a><a href="#incident-handoff">Incident handoff</a>${incidentLink}</div><section class="grid"><div class="card"><span>Events</span><strong>${String(record.summary.eventCount)}</strong></div><div class="card"><span>Turns</span><strong>${String(record.summary.turnCount)}</strong></div><div class="card"><span>Errors</span><strong>${String(record.summary.errorCount)}</strong></div><div class="card"><span>Duration</span><strong>${formatMs5(record.summary.callDurationMs)}</strong></div><div class="card"><span>Provider recovery</span><strong>${escapeHtml48(providerDecisionSummary.recoveryStatus)}</strong><span>${String(providerDecisionSummary.fallbacks)} fallback / ${String(providerDecisionSummary.degraded)} degraded / ${String(providerDecisionSummary.errors)} errors</span></div><div class="card"><span>Telephony media</span><strong>${String(record.telephonyMedia.media)}</strong><span>${String(record.telephonyMedia.inbound)} inbound / ${String(record.telephonyMedia.outbound)} outbound / ${String(record.telephonyMedia.clears)} clears</span></div><div class="card"><span>Guardrails</span><strong>${String(record.guardrails.blocked)}</strong></div><div class="card"><span>Audit</span><strong>${String(record.audit?.total ?? 0)}</strong></div><div class="card"><span>Reviews</span><strong>${String(record.reviews?.total ?? 0)}</strong></div><div class="card"><span>Tasks</span><strong>${String(record.tasks?.total ?? 0)}</strong></div><div class="card"><span>Integrations</span><strong>${String(record.integrationEvents?.total ?? 0)}</strong></div></section><section class="two-column"><div><h2 id="transcript">Transcript</h2><ul>${transcript}</ul></div><div><h2 id="provider-decisions">Provider Decisions</h2><ul>${providerDecisions}</ul></div></section><section id="telephony-media"><h2>Telephony Media</h2><p class="muted">Live <code>client.telephony_media</code> stream lifecycle evidence attached to this session. Carriers: ${escapeHtml48(record.telephonyMedia.carriers.join(", ") || "none")}. Streams: ${escapeHtml48(record.telephonyMedia.streamIds.join(", ") || "none")}. Inbound: ${String(record.telephonyMedia.inbound)}. Outbound: ${String(record.telephonyMedia.outbound)}. Marks: ${String(record.telephonyMedia.marks)}. Clears: ${String(record.telephonyMedia.clears)}.</p><ul>${telephonyMedia}</ul></section><section id="guardrails"><h2>Guardrail Evidence</h2><p class="muted">Live <code>assistant.guardrail</code> decisions attached to this session.</p><ul>${guardrails}</ul></section><section id="incident-handoff"><h2>Copyable Incident Handoff</h2><p class="muted">Paste this into Slack, Linear, Zendesk, or an incident review. ${incidentLink}</p><pre><code>${incidentMarkdown}</code></pre></section><section class="primitive"><p class="eyebrow">Copy into your app</p><h2><code>createVoiceOperationsRecordRoutes(...)</code> gives every call one debuggable object</h2><p class="muted">Use this as the support/debug payload across traces, provider routing, tools, handoffs, guardrails, audit, latency, replay, reviews, tasks, media streams, and webhook delivery.</p><pre><code>${snippet}</code></pre></section><section><h2>Provider Summary</h2><div class="grid">${providers}</div></section><section><h2>Handoffs</h2><ul>${handoffs}</ul></section><section><h2>Tools</h2><ul>${tools}</ul></section><section><h2>Reviews</h2><ul>${reviews}</ul></section><section><h2>Tasks</h2><ul>${tasks}</ul></section><section><h2>Integration Events</h2><ul>${integrationEvents}</ul></section></main></body></html>`;
|
|
28254
28406
|
};
|
|
28255
28407
|
var createVoiceOperationsRecordRoutes = (options) => {
|
|
28256
28408
|
const path = options.path ?? "/api/voice-operations/:sessionId";
|
|
@@ -28323,7 +28475,7 @@ var assertVoiceObservabilityExportSchema = (input) => {
|
|
|
28323
28475
|
};
|
|
28324
28476
|
var isRecord2 = (value) => Boolean(value) && typeof value === "object" && !Array.isArray(value);
|
|
28325
28477
|
var isStatus2 = (value) => value === "fail" || value === "pass" || value === "warn";
|
|
28326
|
-
var
|
|
28478
|
+
var getRecord2 = (value, key) => isRecord2(value) && isRecord2(value[key]) ? value[key] : undefined;
|
|
28327
28479
|
var getRecordArray = (value, key) => isRecord2(value) && Array.isArray(value[key]) ? value[key] : undefined;
|
|
28328
28480
|
var inferVoiceObservabilityExportRecordKind = (record) => {
|
|
28329
28481
|
if (isRecord2(record.manifest) && isRecord2(record.artifactIndex)) {
|
|
@@ -28347,7 +28499,7 @@ var pushValidationIssue = (issues, issue) => {
|
|
|
28347
28499
|
issues.push(issue);
|
|
28348
28500
|
};
|
|
28349
28501
|
var requireRecordSchema = (issues, record, path) => {
|
|
28350
|
-
const schema =
|
|
28502
|
+
const schema = getRecord2(record, "schema");
|
|
28351
28503
|
if (schema?.id !== voiceObservabilityExportSchemaId || schema?.version !== voiceObservabilityExportSchemaVersion) {
|
|
28352
28504
|
pushValidationIssue(issues, {
|
|
28353
28505
|
code: "voice.observability.export.unsupported_schema",
|
|
@@ -118,6 +118,31 @@ export type VoiceOperationsRecordGuardrailSummary = {
|
|
|
118
118
|
total: number;
|
|
119
119
|
warned: number;
|
|
120
120
|
};
|
|
121
|
+
export type VoiceOperationsRecordTelephonyMediaEvent = {
|
|
122
|
+
at: number;
|
|
123
|
+
audioBytes: number;
|
|
124
|
+
callSid?: string;
|
|
125
|
+
carrier?: string;
|
|
126
|
+
direction?: string;
|
|
127
|
+
event: string;
|
|
128
|
+
sequenceNumber?: string;
|
|
129
|
+
streamId?: string;
|
|
130
|
+
};
|
|
131
|
+
export type VoiceOperationsRecordTelephonyMediaSummary = {
|
|
132
|
+
audioBytes: number;
|
|
133
|
+
carriers: string[];
|
|
134
|
+
clears: number;
|
|
135
|
+
errors: number;
|
|
136
|
+
events: VoiceOperationsRecordTelephonyMediaEvent[];
|
|
137
|
+
inbound: number;
|
|
138
|
+
marks: number;
|
|
139
|
+
media: number;
|
|
140
|
+
outbound: number;
|
|
141
|
+
starts: number;
|
|
142
|
+
stops: number;
|
|
143
|
+
streamIds: string[];
|
|
144
|
+
total: number;
|
|
145
|
+
};
|
|
121
146
|
export type VoiceOperationsRecordGuardrailAssertionInput = {
|
|
122
147
|
minBlocked?: number;
|
|
123
148
|
minDecisions?: number;
|
|
@@ -186,6 +211,7 @@ export type VoiceOperationsRecord = {
|
|
|
186
211
|
status: VoiceOperationsRecordStatus;
|
|
187
212
|
summary: VoiceTraceSummary;
|
|
188
213
|
tasks?: VoiceOperationsRecordTaskSummary;
|
|
214
|
+
telephonyMedia: VoiceOperationsRecordTelephonyMediaSummary;
|
|
189
215
|
timeline: VoiceTraceTimelineEvent[];
|
|
190
216
|
tools: VoiceOperationsRecordTool[];
|
|
191
217
|
traceEvents: StoredVoiceTraceEvent[];
|
package/dist/testing/index.js
CHANGED
|
@@ -10130,12 +10130,35 @@ var createTwilioSocketAdapter = (socket, getState) => ({
|
|
|
10130
10130
|
}
|
|
10131
10131
|
if (message.type === "audio") {
|
|
10132
10132
|
const payload = transcodePCMToTwilioOutboundPayload(Uint8Array.from(Buffer3.from(message.chunkBase64, "base64")), message.format);
|
|
10133
|
+
const outboundMessage = {
|
|
10134
|
+
event: "media",
|
|
10135
|
+
media: {
|
|
10136
|
+
payload,
|
|
10137
|
+
track: "outbound"
|
|
10138
|
+
},
|
|
10139
|
+
streamSid: state.streamSid
|
|
10140
|
+
};
|
|
10133
10141
|
state.hasOutboundAudioSinceLastInbound = true;
|
|
10134
10142
|
state.reviewRecorder?.recordTwilioOutbound({
|
|
10135
10143
|
bytes: payload.length,
|
|
10136
10144
|
event: "media",
|
|
10137
10145
|
track: "outbound"
|
|
10138
10146
|
});
|
|
10147
|
+
await state.trace?.append({
|
|
10148
|
+
at: Date.now(),
|
|
10149
|
+
payload: {
|
|
10150
|
+
audioBytes: Buffer3.from(payload, "base64").byteLength,
|
|
10151
|
+
callSid: state.callSid ?? undefined,
|
|
10152
|
+
carrier: state.carrier,
|
|
10153
|
+
direction: "outbound",
|
|
10154
|
+
envelope: outboundMessage,
|
|
10155
|
+
event: "media",
|
|
10156
|
+
streamId: state.streamSid
|
|
10157
|
+
},
|
|
10158
|
+
scenarioId: state.scenarioId ?? undefined,
|
|
10159
|
+
sessionId: state.sessionId ?? state.streamSid,
|
|
10160
|
+
type: "client.telephony_media"
|
|
10161
|
+
});
|
|
10139
10162
|
await Promise.resolve(socket.send(JSON.stringify({
|
|
10140
10163
|
event: "media",
|
|
10141
10164
|
media: {
|
|
@@ -10146,17 +10169,32 @@ var createTwilioSocketAdapter = (socket, getState) => ({
|
|
|
10146
10169
|
return;
|
|
10147
10170
|
}
|
|
10148
10171
|
if (message.type === "assistant" && message.turnId) {
|
|
10149
|
-
|
|
10150
|
-
event: "mark",
|
|
10151
|
-
name: `assistant:${message.turnId}`
|
|
10152
|
-
});
|
|
10153
|
-
await Promise.resolve(socket.send(JSON.stringify({
|
|
10172
|
+
const outboundMessage = {
|
|
10154
10173
|
event: "mark",
|
|
10155
10174
|
mark: {
|
|
10156
10175
|
name: `assistant:${message.turnId}`
|
|
10157
10176
|
},
|
|
10158
10177
|
streamSid: state.streamSid
|
|
10159
|
-
}
|
|
10178
|
+
};
|
|
10179
|
+
state.reviewRecorder?.recordTwilioOutbound({
|
|
10180
|
+
event: "mark",
|
|
10181
|
+
name: `assistant:${message.turnId}`
|
|
10182
|
+
});
|
|
10183
|
+
await state.trace?.append({
|
|
10184
|
+
at: Date.now(),
|
|
10185
|
+
payload: {
|
|
10186
|
+
callSid: state.callSid ?? undefined,
|
|
10187
|
+
carrier: state.carrier,
|
|
10188
|
+
direction: "outbound",
|
|
10189
|
+
envelope: outboundMessage,
|
|
10190
|
+
event: "mark",
|
|
10191
|
+
streamId: state.streamSid
|
|
10192
|
+
},
|
|
10193
|
+
scenarioId: state.scenarioId ?? undefined,
|
|
10194
|
+
sessionId: state.sessionId ?? state.streamSid,
|
|
10195
|
+
type: "client.telephony_media"
|
|
10196
|
+
});
|
|
10197
|
+
await Promise.resolve(socket.send(JSON.stringify(outboundMessage)));
|
|
10160
10198
|
}
|
|
10161
10199
|
}
|
|
10162
10200
|
});
|
|
@@ -10179,6 +10217,7 @@ var createTwilioMediaStreamBridge = (socket, options) => {
|
|
|
10179
10217
|
};
|
|
10180
10218
|
const bridgeState = {
|
|
10181
10219
|
callSid: null,
|
|
10220
|
+
carrier: options.telephonyMediaCarrier ?? "twilio",
|
|
10182
10221
|
hasOutboundAudioSinceLastInbound: false,
|
|
10183
10222
|
onVoiceMessage: options.onVoiceMessage,
|
|
10184
10223
|
reviewRecorder: options.review ? createVoiceCallReviewRecorder({
|
|
@@ -10198,11 +10237,12 @@ var createTwilioMediaStreamBridge = (socket, options) => {
|
|
|
10198
10237
|
}) : undefined,
|
|
10199
10238
|
scenarioId: options.scenarioId ?? null,
|
|
10200
10239
|
sessionId: options.sessionId ?? null,
|
|
10201
|
-
streamSid: null
|
|
10240
|
+
streamSid: null,
|
|
10241
|
+
trace: options.trace
|
|
10202
10242
|
};
|
|
10203
10243
|
let sessionHandle = null;
|
|
10204
10244
|
let reviewArtifactDelivered = false;
|
|
10205
|
-
const telephonyMediaCarrier =
|
|
10245
|
+
const telephonyMediaCarrier = bridgeState.carrier;
|
|
10206
10246
|
const appendTelephonyMediaTrace = async (message, override) => {
|
|
10207
10247
|
const trace = options.trace;
|
|
10208
10248
|
const sessionId = override?.sessionId ?? bridgeState.sessionId ?? (message.event === "start" ? message.start.customParameters?.sessionId : undefined) ?? (message.event === "start" ? message.start.streamSid : "telephony-media");
|
|
@@ -10336,13 +10376,28 @@ var createTwilioMediaStreamBridge = (socket, options) => {
|
|
|
10336
10376
|
track: message.media.track
|
|
10337
10377
|
});
|
|
10338
10378
|
if (options.clearOnInboundMedia !== false && bridgeState.hasOutboundAudioSinceLastInbound && bridgeState.streamSid) {
|
|
10379
|
+
const outboundMessage = {
|
|
10380
|
+
event: "clear",
|
|
10381
|
+
streamSid: bridgeState.streamSid
|
|
10382
|
+
};
|
|
10339
10383
|
bridgeState.reviewRecorder?.recordTwilioOutbound({
|
|
10340
10384
|
event: "clear"
|
|
10341
10385
|
});
|
|
10342
|
-
await
|
|
10343
|
-
|
|
10344
|
-
|
|
10345
|
-
|
|
10386
|
+
await options.trace?.append({
|
|
10387
|
+
at: Date.now(),
|
|
10388
|
+
payload: {
|
|
10389
|
+
callSid: bridgeState.callSid ?? undefined,
|
|
10390
|
+
carrier: telephonyMediaCarrier,
|
|
10391
|
+
direction: "outbound",
|
|
10392
|
+
envelope: outboundMessage,
|
|
10393
|
+
event: "clear",
|
|
10394
|
+
streamId: bridgeState.streamSid
|
|
10395
|
+
},
|
|
10396
|
+
scenarioId: bridgeState.scenarioId ?? undefined,
|
|
10397
|
+
sessionId: bridgeState.sessionId ?? bridgeState.streamSid,
|
|
10398
|
+
type: "client.telephony_media"
|
|
10399
|
+
});
|
|
10400
|
+
await Promise.resolve(socket.send(JSON.stringify(outboundMessage)));
|
|
10346
10401
|
}
|
|
10347
10402
|
bridgeState.hasOutboundAudioSinceLastInbound = false;
|
|
10348
10403
|
await appendTelephonyMediaTrace(message, {
|