twilio-agent-connect 2.0.0 → 2.1.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +16 -13
- package/dist/index.d.ts +581 -25
- package/dist/index.js +755 -124
- package/dist/index.js.map +1 -1
- package/package.json +6 -1
package/dist/index.js
CHANGED
|
@@ -33,6 +33,8 @@ var voicePathSchema = (defaultPath) => z.preprocess((v) => {
|
|
|
33
33
|
const trimmed = v.trim();
|
|
34
34
|
return trimmed.length === 0 ? void 0 : trimmed;
|
|
35
35
|
}, z.string().startsWith("/", 'Path must start with "/"').default(defaultPath));
|
|
36
|
+
var CallEventKindSchema = z.enum(["status", "amd", "recording"]);
|
|
37
|
+
var CALL_EVENT_KINDS = CallEventKindSchema.options;
|
|
36
38
|
var TACConfigSchema = z.object({
|
|
37
39
|
accountSid: z.string().min(1, "Twilio Account SID is required"),
|
|
38
40
|
authToken: z.string().min(1, "Twilio Auth Token is required"),
|
|
@@ -88,8 +90,14 @@ var TACConfigSchema = z.object({
|
|
|
88
90
|
* Must start with '/'.
|
|
89
91
|
*/
|
|
90
92
|
voiceActionPath: voicePathSchema("/conversation-relay-callback"),
|
|
93
|
+
/**
|
|
94
|
+
* Base path for the call-event callbacks (status, async AMD, recording).
|
|
95
|
+
* TACServer registers one route per callback under it — `<base>/status`,
|
|
96
|
+
* `<base>/amd`, `<base>/recording` — so the route identifies the event. Same
|
|
97
|
+
* role as voiceActionPath. Must start with '/'.
|
|
98
|
+
*/
|
|
99
|
+
voiceCallEventPath: voicePathSchema("/twilio/call-events"),
|
|
91
100
|
cintelConfigurationId: z.string().optional(),
|
|
92
|
-
cintelObservationOperatorSid: z.string().optional(),
|
|
93
101
|
cintelSummaryOperatorSid: z.string().optional(),
|
|
94
102
|
region: z.string().max(63, "Invalid Twilio region format (must be a valid DNS label)").regex(
|
|
95
103
|
/^[a-z0-9](?:[a-z0-9-]*[a-z0-9])?$/,
|
|
@@ -124,8 +132,8 @@ var EnvironmentVariables = {
|
|
|
124
132
|
TWILIO_VOICE_PUBLIC_DOMAIN: "TWILIO_VOICE_PUBLIC_DOMAIN",
|
|
125
133
|
TWILIO_VOICE_WEBSOCKET_PATH: "TWILIO_VOICE_WEBSOCKET_PATH",
|
|
126
134
|
TWILIO_VOICE_ACTION_PATH: "TWILIO_VOICE_ACTION_PATH",
|
|
135
|
+
TWILIO_VOICE_CALL_EVENT_PATH: "TWILIO_VOICE_CALL_EVENT_PATH",
|
|
127
136
|
TWILIO_TAC_CI_CONFIGURATION_ID: "TWILIO_TAC_CI_CONFIGURATION_ID",
|
|
128
|
-
TWILIO_TAC_CI_OBSERVATION_OPERATOR_SID: "TWILIO_TAC_CI_OBSERVATION_OPERATOR_SID",
|
|
129
137
|
TWILIO_TAC_CI_SUMMARY_OPERATOR_SID: "TWILIO_TAC_CI_SUMMARY_OPERATOR_SID",
|
|
130
138
|
TWILIO_REGION: "TWILIO_REGION",
|
|
131
139
|
TWILIO_STUDIO_HANDOFF_FLOW_SID: "TWILIO_STUDIO_HANDOFF_FLOW_SID"
|
|
@@ -232,6 +240,13 @@ var AuthorInfoSchema = z.object({
|
|
|
232
240
|
});
|
|
233
241
|
var ConversationSessionSchema = z.object({
|
|
234
242
|
conversationId: z.string().min(1, "Conversation ID is required"),
|
|
243
|
+
/**
|
|
244
|
+
* Twilio Call SID on the Voice channel, unset on messaging. The correlation
|
|
245
|
+
* key for call events (`VoiceChannel.onCallStatus` / `onAmd` / `onRecording`)
|
|
246
|
+
* and `endCall`. Equals `conversationId` in relay-only mode; look the session
|
|
247
|
+
* up the other way with `VoiceChannel.getConversationSessionByCallSid`.
|
|
248
|
+
*/
|
|
249
|
+
callSid: z.string().optional(),
|
|
235
250
|
profileId: z.string().optional(),
|
|
236
251
|
serviceId: z.string().optional(),
|
|
237
252
|
channel: ChannelTypeSchema,
|
|
@@ -548,7 +563,13 @@ var MemoryChannelTypeSchema = z.enum([
|
|
|
548
563
|
"API",
|
|
549
564
|
"SYSTEM"
|
|
550
565
|
]);
|
|
551
|
-
var MemoryParticipantTypeSchema = z.enum([
|
|
566
|
+
var MemoryParticipantTypeSchema = z.enum([
|
|
567
|
+
"HUMAN_AGENT",
|
|
568
|
+
"CUSTOMER",
|
|
569
|
+
"AI_AGENT",
|
|
570
|
+
"AGENT",
|
|
571
|
+
"UNKNOWN"
|
|
572
|
+
]);
|
|
552
573
|
var MemoryDeliveryStatusSchema = z.enum([
|
|
553
574
|
"INITIATED",
|
|
554
575
|
"IN_PROGRESS",
|
|
@@ -640,11 +661,17 @@ var EMPTY_MEMORY_RESPONSE = {
|
|
|
640
661
|
summaries: [],
|
|
641
662
|
communications: []
|
|
642
663
|
};
|
|
643
|
-
var
|
|
664
|
+
var ObservationCreateRequestSchema = z.object({
|
|
644
665
|
content: z.string(),
|
|
645
666
|
source: z.string(),
|
|
646
667
|
occurredAt: z.string(),
|
|
647
|
-
conversationIds: z.array(z.string())
|
|
668
|
+
conversationIds: z.array(z.string()).optional()
|
|
669
|
+
});
|
|
670
|
+
var CreateObservationsRequestSchema = z.object({
|
|
671
|
+
observations: z.array(ObservationCreateRequestSchema)
|
|
672
|
+
});
|
|
673
|
+
var CreateObservationResponseSchema = z.object({
|
|
674
|
+
message: z.string()
|
|
648
675
|
});
|
|
649
676
|
var CreateConversationSummariesResponseSchema = z.object({
|
|
650
677
|
message: z.string()
|
|
@@ -1026,10 +1053,193 @@ var ConversationRelayCallbackPayloadSchema = z.object({
|
|
|
1026
1053
|
SessionStatus: z.string().optional(),
|
|
1027
1054
|
SessionDuration: z.string().optional()
|
|
1028
1055
|
});
|
|
1056
|
+
function splitCallEventForm(form, aliases) {
|
|
1057
|
+
const known = {};
|
|
1058
|
+
const extra = {};
|
|
1059
|
+
for (const [key, value] of Object.entries(form)) {
|
|
1060
|
+
const alias = aliases[key];
|
|
1061
|
+
if (alias) {
|
|
1062
|
+
known[alias] = value;
|
|
1063
|
+
} else {
|
|
1064
|
+
extra[key] = value;
|
|
1065
|
+
}
|
|
1066
|
+
}
|
|
1067
|
+
return { known, extra };
|
|
1068
|
+
}
|
|
1069
|
+
var CallEventBaseShape = {
|
|
1070
|
+
callSid: z.string().min(1, "CallSid is required"),
|
|
1071
|
+
accountSid: z.string().optional(),
|
|
1072
|
+
/** Any other Twilio webhook fields not surfaced above. */
|
|
1073
|
+
extra: z.record(z.string(), z.string()).default({})
|
|
1074
|
+
};
|
|
1075
|
+
var CALL_EVENT_BASE_ALIASES = {
|
|
1076
|
+
CallSid: "callSid",
|
|
1077
|
+
AccountSid: "accountSid"
|
|
1078
|
+
};
|
|
1079
|
+
var UNREACHED_CALL_STATUSES = ["busy", "no-answer", "failed", "canceled"];
|
|
1080
|
+
var CallStatusEventSchema = z.object({
|
|
1081
|
+
...CallEventBaseShape,
|
|
1082
|
+
callStatus: z.string().optional(),
|
|
1083
|
+
callDuration: z.string().optional(),
|
|
1084
|
+
sipResponseCode: z.string().optional()
|
|
1085
|
+
}).transform((event) => ({
|
|
1086
|
+
...event,
|
|
1087
|
+
/** Call ended without reaching the callee — i.e. worth a retry. */
|
|
1088
|
+
isUnreached: UNREACHED_CALL_STATUSES.includes(event.callStatus ?? "")
|
|
1089
|
+
}));
|
|
1090
|
+
var CALL_STATUS_EVENT_ALIASES = {
|
|
1091
|
+
...CALL_EVENT_BASE_ALIASES,
|
|
1092
|
+
CallStatus: "callStatus",
|
|
1093
|
+
CallDuration: "callDuration",
|
|
1094
|
+
SipResponseCode: "sipResponseCode"
|
|
1095
|
+
};
|
|
1096
|
+
function callStatusEventFromForm(form) {
|
|
1097
|
+
const { known, extra } = splitCallEventForm(form, CALL_STATUS_EVENT_ALIASES);
|
|
1098
|
+
return CallStatusEventSchema.parse({ ...known, extra });
|
|
1099
|
+
}
|
|
1100
|
+
var AmdEventSchema = z.object({
|
|
1101
|
+
...CallEventBaseShape,
|
|
1102
|
+
answeredBy: z.string().optional(),
|
|
1103
|
+
machineDetectionDuration: z.string().optional()
|
|
1104
|
+
}).transform((event) => ({
|
|
1105
|
+
...event,
|
|
1106
|
+
/**
|
|
1107
|
+
* A machine answered — any `machine_*` value, either mode. `unknown`
|
|
1108
|
+
* (detection timed out) is false, so a call is never hung up on a guess.
|
|
1109
|
+
*/
|
|
1110
|
+
isMachine: (event.answeredBy ?? "").startsWith("machine")
|
|
1111
|
+
}));
|
|
1112
|
+
var AMD_EVENT_ALIASES = {
|
|
1113
|
+
...CALL_EVENT_BASE_ALIASES,
|
|
1114
|
+
AnsweredBy: "answeredBy",
|
|
1115
|
+
MachineDetectionDuration: "machineDetectionDuration"
|
|
1116
|
+
};
|
|
1117
|
+
function amdEventFromForm(form) {
|
|
1118
|
+
const { known, extra } = splitCallEventForm(form, AMD_EVENT_ALIASES);
|
|
1119
|
+
return AmdEventSchema.parse({ ...known, extra });
|
|
1120
|
+
}
|
|
1121
|
+
var RecordingEventSchema = z.object({
|
|
1122
|
+
...CallEventBaseShape,
|
|
1123
|
+
recordingSid: z.string().optional(),
|
|
1124
|
+
recordingUrl: z.string().optional(),
|
|
1125
|
+
recordingStatus: z.string().optional(),
|
|
1126
|
+
recordingDuration: z.string().optional()
|
|
1127
|
+
});
|
|
1128
|
+
var RECORDING_EVENT_ALIASES = {
|
|
1129
|
+
...CALL_EVENT_BASE_ALIASES,
|
|
1130
|
+
RecordingSid: "recordingSid",
|
|
1131
|
+
RecordingUrl: "recordingUrl",
|
|
1132
|
+
RecordingStatus: "recordingStatus",
|
|
1133
|
+
RecordingDuration: "recordingDuration"
|
|
1134
|
+
};
|
|
1135
|
+
function recordingEventFromForm(form) {
|
|
1136
|
+
const { known, extra } = splitCallEventForm(form, RECORDING_EVENT_ALIASES);
|
|
1137
|
+
return RecordingEventSchema.parse({ ...known, extra });
|
|
1138
|
+
}
|
|
1139
|
+
var CALLS_CREATE_PARAMS = [
|
|
1140
|
+
"applicationSid",
|
|
1141
|
+
"asyncAmd",
|
|
1142
|
+
"asyncAmdStatusCallback",
|
|
1143
|
+
"asyncAmdStatusCallbackMethod",
|
|
1144
|
+
"byoc",
|
|
1145
|
+
"callerId",
|
|
1146
|
+
"callReason",
|
|
1147
|
+
"callToken",
|
|
1148
|
+
"clientNotificationUrl",
|
|
1149
|
+
"fallbackMethod",
|
|
1150
|
+
"fallbackUrl",
|
|
1151
|
+
"from",
|
|
1152
|
+
"machineDetection",
|
|
1153
|
+
"machineDetectionSilenceTimeout",
|
|
1154
|
+
"machineDetectionSpeechEndThreshold",
|
|
1155
|
+
"machineDetectionSpeechThreshold",
|
|
1156
|
+
"machineDetectionTimeout",
|
|
1157
|
+
"method",
|
|
1158
|
+
"record",
|
|
1159
|
+
"recordingChannels",
|
|
1160
|
+
"recordingStatusCallback",
|
|
1161
|
+
"recordingStatusCallbackEvent",
|
|
1162
|
+
"recordingStatusCallbackMethod",
|
|
1163
|
+
"recordingTrack",
|
|
1164
|
+
"sendDigits",
|
|
1165
|
+
"sipAuthPassword",
|
|
1166
|
+
"sipAuthUsername",
|
|
1167
|
+
"statusCallback",
|
|
1168
|
+
"statusCallbackEvent",
|
|
1169
|
+
"statusCallbackMethod",
|
|
1170
|
+
"timeLimit",
|
|
1171
|
+
"timeout",
|
|
1172
|
+
"to",
|
|
1173
|
+
"trim",
|
|
1174
|
+
"twiml",
|
|
1175
|
+
"url"
|
|
1176
|
+
];
|
|
1177
|
+
var RESERVED_CALL_PARAMS = ["to", "from", "twiml", "url", "applicationSid"];
|
|
1178
|
+
var ACCEPTED_CALL_PARAMS = new Set(CALLS_CREATE_PARAMS);
|
|
1179
|
+
function isAsyncAmdEnabled(value) {
|
|
1180
|
+
if (typeof value === "boolean") return value;
|
|
1181
|
+
if (typeof value === "string") return value.trim().toLowerCase() === "true";
|
|
1182
|
+
return false;
|
|
1183
|
+
}
|
|
1184
|
+
var CallOptionsObjectSchema = z.looseObject({
|
|
1185
|
+
machineDetection: z.enum(["Enable", "DetectMessageEnd"]).optional(),
|
|
1186
|
+
asyncAmd: z.union([z.boolean(), z.string()]).optional(),
|
|
1187
|
+
asyncAmdStatusCallback: z.string().optional(),
|
|
1188
|
+
asyncAmdStatusCallbackMethod: z.string().optional(),
|
|
1189
|
+
machineDetectionTimeout: z.number().int().optional(),
|
|
1190
|
+
machineDetectionSpeechThreshold: z.number().int().optional(),
|
|
1191
|
+
machineDetectionSpeechEndThreshold: z.number().int().optional(),
|
|
1192
|
+
machineDetectionSilenceTimeout: z.number().int().optional(),
|
|
1193
|
+
record: z.boolean().optional(),
|
|
1194
|
+
recordingStatusCallback: z.string().optional(),
|
|
1195
|
+
recordingStatusCallbackEvent: z.array(z.string()).optional(),
|
|
1196
|
+
recordingChannels: z.string().optional(),
|
|
1197
|
+
recordingTrack: z.string().optional(),
|
|
1198
|
+
statusCallback: z.string().optional(),
|
|
1199
|
+
statusCallbackEvent: z.array(z.string()).optional(),
|
|
1200
|
+
statusCallbackMethod: z.string().optional(),
|
|
1201
|
+
timeout: z.number().int().optional()
|
|
1202
|
+
}).superRefine((value, ctx) => {
|
|
1203
|
+
const supplied = Object.keys(value);
|
|
1204
|
+
const conflict = supplied.filter(
|
|
1205
|
+
(key) => RESERVED_CALL_PARAMS.includes(key)
|
|
1206
|
+
);
|
|
1207
|
+
if (conflict.length > 0) {
|
|
1208
|
+
ctx.addIssue({
|
|
1209
|
+
code: "custom",
|
|
1210
|
+
message: `callOptions may not set TAC-owned call parameters: ${conflict.sort().join(", ")}. TAC builds the call and its TwiML.`
|
|
1211
|
+
});
|
|
1212
|
+
}
|
|
1213
|
+
const unknown = supplied.filter((key) => !ACCEPTED_CALL_PARAMS.has(key));
|
|
1214
|
+
if (unknown.length > 0) {
|
|
1215
|
+
ctx.addIssue({
|
|
1216
|
+
code: "custom",
|
|
1217
|
+
message: `callOptions has parameters Twilio's calls.create() does not accept: ${unknown.sort().join(", ")}. Check for a typo, or upgrade the twilio package.`
|
|
1218
|
+
});
|
|
1219
|
+
}
|
|
1220
|
+
if (Boolean(value.machineDetection) !== isAsyncAmdEnabled(value.asyncAmd)) {
|
|
1221
|
+
ctx.addIssue({
|
|
1222
|
+
code: "custom",
|
|
1223
|
+
message: `AMD requires both machineDetection and asyncAmd; got machineDetection=${JSON.stringify(value.machineDetection)}, asyncAmd=${JSON.stringify(value.asyncAmd)}.`
|
|
1224
|
+
});
|
|
1225
|
+
}
|
|
1226
|
+
});
|
|
1227
|
+
var CallOptionsSchema = CallOptionsObjectSchema.transform(
|
|
1228
|
+
(value) => value
|
|
1229
|
+
);
|
|
1230
|
+
function callOptionsToCreateParams(options) {
|
|
1231
|
+
const params = {};
|
|
1232
|
+
for (const [key, value] of Object.entries(options)) {
|
|
1233
|
+
if (value === void 0) continue;
|
|
1234
|
+
params[key] = key === "asyncAmd" && typeof value === "boolean" ? String(value) : value;
|
|
1235
|
+
}
|
|
1236
|
+
return params;
|
|
1237
|
+
}
|
|
1029
1238
|
var InitiateVoiceConversationOptionsSchema = z.object({
|
|
1030
1239
|
to: z.string().min(1, "Recipient phone number is required"),
|
|
1031
1240
|
websocketUrl: z.url().optional(),
|
|
1032
|
-
twimlOptions: TwiMLOptionsSchema.optional()
|
|
1241
|
+
twimlOptions: TwiMLOptionsSchema.optional(),
|
|
1242
|
+
callOptions: CallOptionsSchema.optional()
|
|
1033
1243
|
}).strict();
|
|
1034
1244
|
var JSONSchemaSchema = z.object({
|
|
1035
1245
|
type: z.enum(["object", "string", "number", "boolean", "array"]),
|
|
@@ -1047,6 +1257,11 @@ var OpenAIToolSchema = z.object({
|
|
|
1047
1257
|
parameters: JSONSchemaSchema
|
|
1048
1258
|
})
|
|
1049
1259
|
});
|
|
1260
|
+
var AnthropicToolSchema = z.object({
|
|
1261
|
+
name: z.string(),
|
|
1262
|
+
description: z.string(),
|
|
1263
|
+
input_schema: JSONSchemaSchema
|
|
1264
|
+
});
|
|
1050
1265
|
var ToolExecutionResultSchema = z.object({
|
|
1051
1266
|
success: z.boolean(),
|
|
1052
1267
|
data: z.any().optional(),
|
|
@@ -1101,7 +1316,6 @@ var OperatorProcessingResultSchema = z.object({
|
|
|
1101
1316
|
});
|
|
1102
1317
|
var ConversationIntelligenceConfigSchema = z.object({
|
|
1103
1318
|
configurationId: z.string(),
|
|
1104
|
-
observationOperatorSid: z.string().optional(),
|
|
1105
1319
|
summaryOperatorSid: z.string().optional()
|
|
1106
1320
|
});
|
|
1107
1321
|
var ConversationSummaryItemSchema = z.object({
|
|
@@ -1152,8 +1366,9 @@ var TACConfig = class _TACConfig {
|
|
|
1152
1366
|
voiceWebsocketPath;
|
|
1153
1367
|
/** Path the ConversationRelay action callback is served at (default '/conversation-relay-callback'). */
|
|
1154
1368
|
voiceActionPath;
|
|
1369
|
+
/** Base path the call-event callbacks are served under (default '/twilio/call-events'). */
|
|
1370
|
+
voiceCallEventPath;
|
|
1155
1371
|
cintelConfigurationId;
|
|
1156
|
-
cintelObservationOperatorSid;
|
|
1157
1372
|
cintelSummaryOperatorSid;
|
|
1158
1373
|
/** Optional Twilio region subdomain for API routing (e.g. transforms base URLs to `https://{product}.{region}.twilio.com`) */
|
|
1159
1374
|
region;
|
|
@@ -1187,12 +1402,10 @@ var TACConfig = class _TACConfig {
|
|
|
1187
1402
|
}
|
|
1188
1403
|
this.voiceWebsocketPath = validatedConfig.voiceWebsocketPath;
|
|
1189
1404
|
this.voiceActionPath = validatedConfig.voiceActionPath;
|
|
1405
|
+
this.voiceCallEventPath = validatedConfig.voiceCallEventPath;
|
|
1190
1406
|
if (validatedConfig.cintelConfigurationId) {
|
|
1191
1407
|
this.cintelConfigurationId = validatedConfig.cintelConfigurationId;
|
|
1192
1408
|
}
|
|
1193
|
-
if (validatedConfig.cintelObservationOperatorSid) {
|
|
1194
|
-
this.cintelObservationOperatorSid = validatedConfig.cintelObservationOperatorSid;
|
|
1195
|
-
}
|
|
1196
1409
|
if (validatedConfig.cintelSummaryOperatorSid) {
|
|
1197
1410
|
this.cintelSummaryOperatorSid = validatedConfig.cintelSummaryOperatorSid;
|
|
1198
1411
|
}
|
|
@@ -1219,6 +1432,7 @@ var TACConfig = class _TACConfig {
|
|
|
1219
1432
|
* - TWILIO_VOICE_PUBLIC_DOMAIN: Public domain for voice routes (required for voice; domain only, without protocol/port/path, e.g., 'abc123.ngrok.app')
|
|
1220
1433
|
* - TWILIO_VOICE_WEBSOCKET_PATH: Path for the voice WebSocket (default: /ws)
|
|
1221
1434
|
* - TWILIO_VOICE_ACTION_PATH: Path for the ConversationRelay action callback (default: /conversation-relay-callback)
|
|
1435
|
+
* - TWILIO_VOICE_CALL_EVENT_PATH: Base path for the call-event callbacks — status, async AMD, recording (default: /twilio/call-events)
|
|
1222
1436
|
* - TWILIO_REGION: Twilio region subdomain for API routing (e.g. transforms base URLs to `https://{product}.{region}.twilio.com`)
|
|
1223
1437
|
* - TWILIO_STUDIO_HANDOFF_FLOW_SID: Studio Flow SID used by createStudioHandoffTool for human handoff
|
|
1224
1438
|
* - TWILIO_RCS_SENDER_ID: RCS Sender ID for the RCS channel
|
|
@@ -1330,17 +1544,37 @@ var TACConfig = class _TACConfig {
|
|
|
1330
1544
|
voicePublicDomain: process.env[EnvironmentVariables.TWILIO_VOICE_PUBLIC_DOMAIN],
|
|
1331
1545
|
voiceWebsocketPath: process.env[EnvironmentVariables.TWILIO_VOICE_WEBSOCKET_PATH] || void 0,
|
|
1332
1546
|
voiceActionPath: process.env[EnvironmentVariables.TWILIO_VOICE_ACTION_PATH] || void 0,
|
|
1547
|
+
voiceCallEventPath: process.env[EnvironmentVariables.TWILIO_VOICE_CALL_EVENT_PATH] || void 0,
|
|
1333
1548
|
cintelConfigurationId: process.env[EnvironmentVariables.TWILIO_TAC_CI_CONFIGURATION_ID],
|
|
1334
|
-
cintelObservationOperatorSid: process.env[EnvironmentVariables.TWILIO_TAC_CI_OBSERVATION_OPERATOR_SID],
|
|
1335
1549
|
cintelSummaryOperatorSid: process.env[EnvironmentVariables.TWILIO_TAC_CI_SUMMARY_OPERATOR_SID],
|
|
1336
1550
|
region: process.env[EnvironmentVariables.TWILIO_REGION],
|
|
1337
1551
|
studioHandoffFlowSid: process.env[EnvironmentVariables.TWILIO_STUDIO_HANDOFF_FLOW_SID]
|
|
1338
1552
|
};
|
|
1339
1553
|
return new _TACConfig(rawConfig);
|
|
1340
1554
|
}
|
|
1555
|
+
/**
|
|
1556
|
+
* Path a call-event callback is served at.
|
|
1557
|
+
*
|
|
1558
|
+
* Single source of truth: the voice channel builds callback URLs from this
|
|
1559
|
+
* and TACServer registers routes at it, so the two can't drift.
|
|
1560
|
+
*/
|
|
1561
|
+
callEventPath(kind) {
|
|
1562
|
+
return `${this.voiceCallEventPath.replace(/\/+$/, "")}/${kind}`;
|
|
1563
|
+
}
|
|
1564
|
+
/**
|
|
1565
|
+
* Public URL for a call-event callback, or `undefined` without a public domain.
|
|
1566
|
+
*/
|
|
1567
|
+
callEventUrl(kind) {
|
|
1568
|
+
if (!this.voicePublicDomain) {
|
|
1569
|
+
return void 0;
|
|
1570
|
+
}
|
|
1571
|
+
return `https://${this.voicePublicDomain}${this.callEventPath(kind)}`;
|
|
1572
|
+
}
|
|
1341
1573
|
/**
|
|
1342
1574
|
* Whether Conversation Orchestrator is configured.
|
|
1343
1575
|
* Returns false in voice-only mode (no conversationConfigurationId).
|
|
1576
|
+
*
|
|
1577
|
+
* @internal
|
|
1344
1578
|
*/
|
|
1345
1579
|
isOrchestratorEnabled() {
|
|
1346
1580
|
return this.conversationConfigurationId !== void 0;
|
|
@@ -1425,6 +1659,13 @@ function maskAddress(address) {
|
|
|
1425
1659
|
if (address.length <= 1) return "***";
|
|
1426
1660
|
return `${address[0]}***`;
|
|
1427
1661
|
}
|
|
1662
|
+
function redactTwimlParameters(twiml) {
|
|
1663
|
+
if (!twiml) return "";
|
|
1664
|
+
const parameterValueRe = /(<Parameter\b[^>]*?\bvalue=)(["'])(.*?)\2/gi;
|
|
1665
|
+
return twiml.replace(parameterValueRe, (_match, prefix, quote) => {
|
|
1666
|
+
return `${prefix}${quote}***${quote}`;
|
|
1667
|
+
});
|
|
1668
|
+
}
|
|
1428
1669
|
|
|
1429
1670
|
// packages/core/src/lib/logger.ts
|
|
1430
1671
|
function piiLogMethod(args, method) {
|
|
@@ -1444,7 +1685,7 @@ function createLogger(options) {
|
|
|
1444
1685
|
|
|
1445
1686
|
// package.json
|
|
1446
1687
|
var package_default = {
|
|
1447
|
-
version: "2.
|
|
1688
|
+
version: "2.1.0"};
|
|
1448
1689
|
function buildUserAgent() {
|
|
1449
1690
|
return `twilio-agent-connect-typescript/${package_default.version}`;
|
|
1450
1691
|
}
|
|
@@ -1525,7 +1766,7 @@ var BaseClient = class {
|
|
|
1525
1766
|
};
|
|
1526
1767
|
|
|
1527
1768
|
// packages/core/src/clients/memory.ts
|
|
1528
|
-
var MemoryClient = class extends BaseClient {
|
|
1769
|
+
var MemoryClient = class _MemoryClient extends BaseClient {
|
|
1529
1770
|
storeId;
|
|
1530
1771
|
constructor(config, storeId, logger) {
|
|
1531
1772
|
const baseUrl = config.region ? `https://memory.${config.region}.twilio.com` : "https://memory.twilio.com";
|
|
@@ -1578,28 +1819,46 @@ var MemoryClient = class extends BaseClient {
|
|
|
1578
1819
|
},
|
|
1579
1820
|
"Raw memory response received"
|
|
1580
1821
|
);
|
|
1581
|
-
|
|
1582
|
-
if (!validatedResponse.success) {
|
|
1822
|
+
if (data === null || typeof data !== "object") {
|
|
1583
1823
|
this.logger.warn(
|
|
1584
1824
|
{
|
|
1585
1825
|
profile_id: profileId,
|
|
1586
|
-
memory_store_id: this.storeId
|
|
1587
|
-
validation_errors: validatedResponse.error.issues
|
|
1826
|
+
memory_store_id: this.storeId
|
|
1588
1827
|
},
|
|
1589
1828
|
"Invalid memory response format"
|
|
1590
1829
|
);
|
|
1591
1830
|
return EMPTY_MEMORY_RESPONSE;
|
|
1592
1831
|
}
|
|
1832
|
+
const response = data;
|
|
1833
|
+
const observations = this.parseItems(
|
|
1834
|
+
response.observations,
|
|
1835
|
+
ObservationInfoSchema,
|
|
1836
|
+
"observation",
|
|
1837
|
+
profileId
|
|
1838
|
+
);
|
|
1839
|
+
const summaries = this.parseItems(
|
|
1840
|
+
response.summaries,
|
|
1841
|
+
SummaryInfoSchema,
|
|
1842
|
+
"summary",
|
|
1843
|
+
profileId
|
|
1844
|
+
);
|
|
1845
|
+
const communications = this.parseItems(
|
|
1846
|
+
response.communications,
|
|
1847
|
+
MemoryCommunicationSchema,
|
|
1848
|
+
"communication",
|
|
1849
|
+
profileId
|
|
1850
|
+
);
|
|
1593
1851
|
this.logger.debug(
|
|
1594
1852
|
{
|
|
1595
1853
|
memory_store_id: this.storeId,
|
|
1596
1854
|
profile_id: profileId,
|
|
1597
|
-
observation_count:
|
|
1598
|
-
summary_count:
|
|
1855
|
+
observation_count: observations.length,
|
|
1856
|
+
summary_count: summaries.length,
|
|
1857
|
+
communication_count: communications.length
|
|
1599
1858
|
},
|
|
1600
1859
|
"Memory retrieval succeeded"
|
|
1601
1860
|
);
|
|
1602
|
-
return
|
|
1861
|
+
return { observations, summaries, communications };
|
|
1603
1862
|
} catch (error) {
|
|
1604
1863
|
this.logger.warn(
|
|
1605
1864
|
{
|
|
@@ -1612,6 +1871,67 @@ var MemoryClient = class extends BaseClient {
|
|
|
1612
1871
|
return EMPTY_MEMORY_RESPONSE;
|
|
1613
1872
|
}
|
|
1614
1873
|
}
|
|
1874
|
+
/** Cap on per-item validation warnings logged per response, to avoid flooding logs. */
|
|
1875
|
+
static MAX_INVALID_ITEM_LOGS = 10;
|
|
1876
|
+
/**
|
|
1877
|
+
* Validate an array of memory items one at a time, dropping (and logging)
|
|
1878
|
+
* any that fail validation while keeping the rest.
|
|
1879
|
+
*
|
|
1880
|
+
* Per-item warnings are capped at {@link MemoryClient.MAX_INVALID_ITEM_LOGS}
|
|
1881
|
+
* and followed by a single summary warning with the total `invalid_count`.
|
|
1882
|
+
*/
|
|
1883
|
+
parseItems(value, schema, itemType, profileId) {
|
|
1884
|
+
if (value === void 0) {
|
|
1885
|
+
return [];
|
|
1886
|
+
}
|
|
1887
|
+
if (!Array.isArray(value)) {
|
|
1888
|
+
this.logger.warn(
|
|
1889
|
+
{
|
|
1890
|
+
profile_id: profileId,
|
|
1891
|
+
memory_store_id: this.storeId,
|
|
1892
|
+
item_type: itemType,
|
|
1893
|
+
received_type: typeof value
|
|
1894
|
+
},
|
|
1895
|
+
"Expected an array of memory items but received a non-array value"
|
|
1896
|
+
);
|
|
1897
|
+
return [];
|
|
1898
|
+
}
|
|
1899
|
+
const parsed = [];
|
|
1900
|
+
let invalidCount = 0;
|
|
1901
|
+
for (const [index, item] of value.entries()) {
|
|
1902
|
+
const result = schema.safeParse(item);
|
|
1903
|
+
if (result.success) {
|
|
1904
|
+
parsed.push(result.data);
|
|
1905
|
+
continue;
|
|
1906
|
+
}
|
|
1907
|
+
invalidCount += 1;
|
|
1908
|
+
if (invalidCount <= _MemoryClient.MAX_INVALID_ITEM_LOGS) {
|
|
1909
|
+
this.logger.warn(
|
|
1910
|
+
{
|
|
1911
|
+
profile_id: profileId,
|
|
1912
|
+
memory_store_id: this.storeId,
|
|
1913
|
+
item_type: itemType,
|
|
1914
|
+
item_index: index,
|
|
1915
|
+
validation_errors: result.error.issues
|
|
1916
|
+
},
|
|
1917
|
+
"Dropping invalid memory item"
|
|
1918
|
+
);
|
|
1919
|
+
}
|
|
1920
|
+
}
|
|
1921
|
+
if (invalidCount > 0) {
|
|
1922
|
+
this.logger.warn(
|
|
1923
|
+
{
|
|
1924
|
+
profile_id: profileId,
|
|
1925
|
+
memory_store_id: this.storeId,
|
|
1926
|
+
item_type: itemType,
|
|
1927
|
+
invalid_count: invalidCount,
|
|
1928
|
+
total_count: value.length
|
|
1929
|
+
},
|
|
1930
|
+
"Dropped invalid memory items"
|
|
1931
|
+
);
|
|
1932
|
+
}
|
|
1933
|
+
return parsed;
|
|
1934
|
+
}
|
|
1615
1935
|
/**
|
|
1616
1936
|
* Find profiles that contain a specific identifier value
|
|
1617
1937
|
*
|
|
@@ -1699,27 +2019,30 @@ var MemoryClient = class extends BaseClient {
|
|
|
1699
2019
|
}
|
|
1700
2020
|
}
|
|
1701
2021
|
/**
|
|
1702
|
-
* Create an observation for a profile
|
|
2022
|
+
* Create an observation for a profile.
|
|
2023
|
+
*
|
|
2024
|
+
* The Memory API Observations endpoint is a batch create: the observation is
|
|
2025
|
+
* wrapped in an `observations` array and `occurredAt` is required, so it
|
|
2026
|
+
* defaults to the current time (ISO 8601) when omitted or blank.
|
|
1703
2027
|
*
|
|
1704
2028
|
* @param profileId - The profile ID to create the observation for
|
|
1705
2029
|
* @param content - The observation content
|
|
1706
2030
|
* @param source - Source of the observation (default: 'conversation-intelligence')
|
|
1707
2031
|
* @param conversationIds - Optional array of conversation IDs associated with this observation
|
|
1708
|
-
* @param occurredAt -
|
|
1709
|
-
* @returns Promise containing the
|
|
2032
|
+
* @param occurredAt - Timestamp when the observation occurred (ISO 8601); defaults to now when omitted or blank
|
|
2033
|
+
* @returns Promise containing the API confirmation message
|
|
1710
2034
|
*/
|
|
1711
2035
|
async createObservation(profileId, content, source = "conversation-intelligence", conversationIds, occurredAt) {
|
|
1712
2036
|
const url = `/v1/Stores/${this.storeId}/Profiles/${profileId}/Observations`;
|
|
1713
|
-
const
|
|
2037
|
+
const observation = {
|
|
1714
2038
|
content,
|
|
1715
|
-
source
|
|
2039
|
+
source,
|
|
2040
|
+
occurredAt: occurredAt && occurredAt.trim() ? occurredAt : (/* @__PURE__ */ new Date()).toISOString()
|
|
1716
2041
|
};
|
|
1717
2042
|
if (conversationIds && conversationIds.length > 0) {
|
|
1718
|
-
|
|
1719
|
-
}
|
|
1720
|
-
if (occurredAt) {
|
|
1721
|
-
requestBody.occurredAt = occurredAt;
|
|
2043
|
+
observation.conversationIds = conversationIds;
|
|
1722
2044
|
}
|
|
2045
|
+
const requestBody = { observations: [observation] };
|
|
1723
2046
|
try {
|
|
1724
2047
|
const data = await this.makeRequest(url, "POST", requestBody);
|
|
1725
2048
|
return CreateObservationResponseSchema.parse(data);
|
|
@@ -2128,22 +2451,6 @@ function generateContent(operatorResult) {
|
|
|
2128
2451
|
const jsonString = JSON.stringify(result);
|
|
2129
2452
|
return jsonString === "{}" || jsonString === "[]" ? void 0 : jsonString;
|
|
2130
2453
|
}
|
|
2131
|
-
function parseObservationsContent(jsonContent) {
|
|
2132
|
-
try {
|
|
2133
|
-
const parsed = JSON.parse(jsonContent);
|
|
2134
|
-
if (typeof parsed === "object" && parsed !== null && "observations" in parsed) {
|
|
2135
|
-
const observations = parsed.observations;
|
|
2136
|
-
if (Array.isArray(observations)) {
|
|
2137
|
-
return observations.filter(
|
|
2138
|
-
(obs) => typeof obs === "string" && obs.trim() !== ""
|
|
2139
|
-
);
|
|
2140
|
-
}
|
|
2141
|
-
}
|
|
2142
|
-
return [];
|
|
2143
|
-
} catch {
|
|
2144
|
-
return [];
|
|
2145
|
-
}
|
|
2146
|
-
}
|
|
2147
2454
|
function parseSummariesContent(jsonContent) {
|
|
2148
2455
|
try {
|
|
2149
2456
|
const parsed = JSON.parse(jsonContent);
|
|
@@ -2281,13 +2588,11 @@ var OperatorResultProcessor = class {
|
|
|
2281
2588
|
*/
|
|
2282
2589
|
async processOperatorResult(event, operatorResult) {
|
|
2283
2590
|
const operatorSid = operatorResult.operator.id;
|
|
2284
|
-
const isObservationOperator = this.config.observationOperatorSid === operatorSid;
|
|
2285
2591
|
const isSummaryOperator = this.config.summaryOperatorSid === operatorSid;
|
|
2286
|
-
if (!
|
|
2592
|
+
if (!isSummaryOperator) {
|
|
2287
2593
|
this.logger.debug(
|
|
2288
2594
|
{
|
|
2289
2595
|
operator_sid: operatorSid,
|
|
2290
|
-
observation_operator_sid: this.config.observationOperatorSid,
|
|
2291
2596
|
summary_operator_sid: this.config.summaryOperatorSid
|
|
2292
2597
|
},
|
|
2293
2598
|
"Skipping unconfigured operator"
|
|
@@ -2325,75 +2630,7 @@ var OperatorResultProcessor = class {
|
|
|
2325
2630
|
createdCount: 0
|
|
2326
2631
|
};
|
|
2327
2632
|
}
|
|
2328
|
-
|
|
2329
|
-
return this.processObservationEvent(event, operatorResult, content, profileIds);
|
|
2330
|
-
} else {
|
|
2331
|
-
return this.processSummaryEvent(event, operatorResult, content, profileIds);
|
|
2332
|
-
}
|
|
2333
|
-
}
|
|
2334
|
-
/**
|
|
2335
|
-
* Process an observation operator result
|
|
2336
|
-
*/
|
|
2337
|
-
async processObservationEvent(event, operatorResult, content, profileIds) {
|
|
2338
|
-
const observations = parseObservationsContent(content);
|
|
2339
|
-
if (observations.length === 0) {
|
|
2340
|
-
this.logger.debug(
|
|
2341
|
-
{ operator_sid: operatorResult.operator.id },
|
|
2342
|
-
"No observations found in content"
|
|
2343
|
-
);
|
|
2344
|
-
return {
|
|
2345
|
-
success: true,
|
|
2346
|
-
eventType: "observation",
|
|
2347
|
-
skipped: true,
|
|
2348
|
-
skipReason: "No observations found in operator result content",
|
|
2349
|
-
createdCount: 0
|
|
2350
|
-
};
|
|
2351
|
-
}
|
|
2352
|
-
let createdCount = 0;
|
|
2353
|
-
for (const profileId of profileIds) {
|
|
2354
|
-
for (const observation of observations) {
|
|
2355
|
-
try {
|
|
2356
|
-
await this.memoryClient.createObservation(
|
|
2357
|
-
profileId,
|
|
2358
|
-
observation,
|
|
2359
|
-
"conversation-intelligence",
|
|
2360
|
-
[event.conversationId],
|
|
2361
|
-
operatorResult.dateCreated
|
|
2362
|
-
);
|
|
2363
|
-
createdCount++;
|
|
2364
|
-
this.logger.debug(
|
|
2365
|
-
{
|
|
2366
|
-
profile_id: profileId,
|
|
2367
|
-
conversation_id: event.conversationId,
|
|
2368
|
-
observation_preview: observation.substring(0, 100)
|
|
2369
|
-
},
|
|
2370
|
-
"Created observation"
|
|
2371
|
-
);
|
|
2372
|
-
} catch (error) {
|
|
2373
|
-
this.logger.error(
|
|
2374
|
-
{
|
|
2375
|
-
err: error,
|
|
2376
|
-
profile_id: profileId,
|
|
2377
|
-
conversation_id: event.conversationId
|
|
2378
|
-
},
|
|
2379
|
-
"Failed to create observation"
|
|
2380
|
-
);
|
|
2381
|
-
return {
|
|
2382
|
-
success: false,
|
|
2383
|
-
eventType: "observation",
|
|
2384
|
-
skipped: false,
|
|
2385
|
-
error: `Failed to create observation: ${error instanceof Error ? error.message : String(error)}`,
|
|
2386
|
-
createdCount
|
|
2387
|
-
};
|
|
2388
|
-
}
|
|
2389
|
-
}
|
|
2390
|
-
}
|
|
2391
|
-
return {
|
|
2392
|
-
success: true,
|
|
2393
|
-
eventType: "observation",
|
|
2394
|
-
skipped: false,
|
|
2395
|
-
createdCount
|
|
2396
|
-
};
|
|
2633
|
+
return this.processSummaryEvent(event, operatorResult, content, profileIds);
|
|
2397
2634
|
}
|
|
2398
2635
|
/**
|
|
2399
2636
|
* Process a summary operator result
|
|
@@ -2515,7 +2752,6 @@ var TAC = class _TAC {
|
|
|
2515
2752
|
tac.memoryClient,
|
|
2516
2753
|
{
|
|
2517
2754
|
configurationId: tac.config.cintelConfigurationId,
|
|
2518
|
-
observationOperatorSid: tac.config.cintelObservationOperatorSid,
|
|
2519
2755
|
summaryOperatorSid: tac.config.cintelSummaryOperatorSid
|
|
2520
2756
|
},
|
|
2521
2757
|
tac.logger.child({ component: "cintel" })
|
|
@@ -2735,6 +2971,8 @@ var TAC = class _TAC {
|
|
|
2735
2971
|
/**
|
|
2736
2972
|
* Whether Conversation Orchestrator is configured.
|
|
2737
2973
|
* Returns false in voice-only mode.
|
|
2974
|
+
*
|
|
2975
|
+
* @internal
|
|
2738
2976
|
*/
|
|
2739
2977
|
isOrchestratorEnabled() {
|
|
2740
2978
|
return this.config.isOrchestratorEnabled();
|
|
@@ -3498,10 +3736,16 @@ var MessagingChannel = class extends BaseChannel {
|
|
|
3498
3736
|
if (!session.aiAgentInfo) {
|
|
3499
3737
|
const resolved = await this.reconcileParticipants(conversationId);
|
|
3500
3738
|
if (!resolved) {
|
|
3501
|
-
|
|
3502
|
-
|
|
3503
|
-
"Reconciliation failed; skipping callback for this inbound"
|
|
3739
|
+
const error = new Error(
|
|
3740
|
+
`Participant reconciliation failed for conversation ${conversationId}; inbound message dropped because no sendable participants could be resolved.`
|
|
3504
3741
|
);
|
|
3742
|
+
error.name = "ParticipantReconciliationError";
|
|
3743
|
+
this.handleError(error, {
|
|
3744
|
+
conversation_id: conversationId,
|
|
3745
|
+
channel: this.channelType,
|
|
3746
|
+
dropped_inbound: true,
|
|
3747
|
+
error_code: "participant_reconciliation_failed"
|
|
3748
|
+
});
|
|
3505
3749
|
return;
|
|
3506
3750
|
}
|
|
3507
3751
|
const [agentParticipant, customerParticipant] = resolved;
|
|
@@ -4446,6 +4690,9 @@ var VoiceChannel = class _VoiceChannel extends BaseChannel {
|
|
|
4446
4690
|
twilioClient;
|
|
4447
4691
|
voiceConfig;
|
|
4448
4692
|
onInboundCallTwimlHandler;
|
|
4693
|
+
onCallStatusHandler;
|
|
4694
|
+
onAmdHandler;
|
|
4695
|
+
onRecordingHandler;
|
|
4449
4696
|
constructor(tac, options) {
|
|
4450
4697
|
super(tac, options);
|
|
4451
4698
|
this.voiceConfig = options ?? {};
|
|
@@ -4481,6 +4728,74 @@ var VoiceChannel = class _VoiceChannel extends BaseChannel {
|
|
|
4481
4728
|
onInboundCallTwiml(callback) {
|
|
4482
4729
|
this.onInboundCallTwimlHandler = callback;
|
|
4483
4730
|
}
|
|
4731
|
+
/**
|
|
4732
|
+
* Register a handler for Twilio `statusCallback` webhooks.
|
|
4733
|
+
*
|
|
4734
|
+
* This is the Calls-API status callback (call disposition), not the
|
|
4735
|
+
* ConversationRelay session callback — see
|
|
4736
|
+
* {@link handleConversationRelayCallback}.
|
|
4737
|
+
*
|
|
4738
|
+
* Registering does two things: it stores the handler, and it makes later
|
|
4739
|
+
* outbound calls pass `statusCallback` to `calls.create`. With no handler
|
|
4740
|
+
* registered TAC omits that parameter, so Twilio has nowhere to post and the
|
|
4741
|
+
* event never arrives.
|
|
4742
|
+
*
|
|
4743
|
+
* Twilio reports only the terminal event by default, which covers every
|
|
4744
|
+
* disposition; set `CallOptions.statusCallbackEvent` for ringing/answered.
|
|
4745
|
+
*
|
|
4746
|
+
* @example
|
|
4747
|
+
* ```typescript
|
|
4748
|
+
* voiceChannel.onCallStatus(async event => {
|
|
4749
|
+
* if (event.isUnreached) {
|
|
4750
|
+
* // queue a retry
|
|
4751
|
+
* }
|
|
4752
|
+
* });
|
|
4753
|
+
* ```
|
|
4754
|
+
*/
|
|
4755
|
+
onCallStatus(callback) {
|
|
4756
|
+
this.onCallStatusHandler = callback;
|
|
4757
|
+
}
|
|
4758
|
+
/**
|
|
4759
|
+
* Register a handler for Twilio `asyncAmdStatusCallback` webhooks.
|
|
4760
|
+
*
|
|
4761
|
+
* Registering makes later outbound calls pass `asyncAmdStatusCallback` to
|
|
4762
|
+
* `calls.create`; without a handler TAC omits it and Twilio has nowhere to
|
|
4763
|
+
* post the result. It does not enable detection — that's per-call, via
|
|
4764
|
+
* `CallOptions.machineDetection` and `asyncAmd`, both of which are required
|
|
4765
|
+
* for this to fire (at most once per call).
|
|
4766
|
+
*
|
|
4767
|
+
* @example
|
|
4768
|
+
* ```typescript
|
|
4769
|
+
* voiceChannel.onAmd(async event => {
|
|
4770
|
+
* if (event.isMachine) {
|
|
4771
|
+
* await voiceChannel.endCall(event.callSid); // voicemail → hang up
|
|
4772
|
+
* }
|
|
4773
|
+
* });
|
|
4774
|
+
* ```
|
|
4775
|
+
*/
|
|
4776
|
+
onAmd(callback) {
|
|
4777
|
+
this.onAmdHandler = callback;
|
|
4778
|
+
}
|
|
4779
|
+
/**
|
|
4780
|
+
* Register a handler for Twilio `recordingStatusCallback` webhooks.
|
|
4781
|
+
*
|
|
4782
|
+
* Registering makes later outbound calls pass `recordingStatusCallback` to
|
|
4783
|
+
* `calls.create`; without a handler TAC omits it and Twilio has nowhere to
|
|
4784
|
+
* post. It does not start recording — that's `CallOptions.record`, which is
|
|
4785
|
+
* required for this to fire.
|
|
4786
|
+
*
|
|
4787
|
+
* @example
|
|
4788
|
+
* ```typescript
|
|
4789
|
+
* voiceChannel.onRecording(async event => {
|
|
4790
|
+
* if (event.recordingStatus === 'completed') {
|
|
4791
|
+
* // store event.recordingUrl
|
|
4792
|
+
* }
|
|
4793
|
+
* });
|
|
4794
|
+
* ```
|
|
4795
|
+
*/
|
|
4796
|
+
onRecording(callback) {
|
|
4797
|
+
this.onRecordingHandler = callback;
|
|
4798
|
+
}
|
|
4484
4799
|
/**
|
|
4485
4800
|
* Resolve the public WebSocket URL from `TACConfig.voicePublicDomain` +
|
|
4486
4801
|
* `TACConfig.voiceWebsocketPath`. Throws if `voicePublicDomain` isn't set.
|
|
@@ -4671,6 +4986,7 @@ var VoiceChannel = class _VoiceChannel extends BaseChannel {
|
|
|
4671
4986
|
this.webSocketConnections.set(conversationId, ws);
|
|
4672
4987
|
this.callSidToConversationId.set(callSid, conversationId);
|
|
4673
4988
|
const session = this.startConversation(conversationId);
|
|
4989
|
+
session.callSid = callSid;
|
|
4674
4990
|
if (fromNumber) {
|
|
4675
4991
|
session.authorInfo = { address: fromNumber };
|
|
4676
4992
|
}
|
|
@@ -4708,6 +5024,7 @@ var VoiceChannel = class _VoiceChannel extends BaseChannel {
|
|
|
4708
5024
|
this.webSocketConnections.set(conversationId, ws);
|
|
4709
5025
|
this.callSidToConversationId.set(callSid, conversationId);
|
|
4710
5026
|
const session = this.startConversation(conversationId, profileId);
|
|
5027
|
+
session.callSid = callSid;
|
|
4711
5028
|
if (customerAddress) {
|
|
4712
5029
|
session.authorInfo = {
|
|
4713
5030
|
address: customerAddress
|
|
@@ -5094,6 +5411,66 @@ var VoiceChannel = class _VoiceChannel extends BaseChannel {
|
|
|
5094
5411
|
// =========================================================================
|
|
5095
5412
|
// Outbound Call Handling
|
|
5096
5413
|
// =========================================================================
|
|
5414
|
+
/**
|
|
5415
|
+
* Overlay `perCall` onto `VoiceChannelConfig.defaultCallOptions`.
|
|
5416
|
+
*
|
|
5417
|
+
* Per-field via key presence, the same convention {@link overlayFields} uses
|
|
5418
|
+
* for TwiML options — so a per-call `{ machineDetection: undefined }`
|
|
5419
|
+
* explicitly clears the channel default rather than falling through to it.
|
|
5420
|
+
*
|
|
5421
|
+
* The result is always validated, for two reasons: a combination only
|
|
5422
|
+
* reachable by layering — per-call clearing `machineDetection` while the
|
|
5423
|
+
* default set `asyncAmd` — must still fail instead of reaching Twilio, and
|
|
5424
|
+
* `VoiceChannelConfig` is a plain interface, so `defaultCallOptions` has had
|
|
5425
|
+
* no runtime validation of its own.
|
|
5426
|
+
*/
|
|
5427
|
+
mergeCallOptions(perCall) {
|
|
5428
|
+
const defaults = this.voiceConfig.defaultCallOptions;
|
|
5429
|
+
if (!defaults && !perCall) {
|
|
5430
|
+
return void 0;
|
|
5431
|
+
}
|
|
5432
|
+
const merged = {};
|
|
5433
|
+
for (const [key, value] of Object.entries(defaults ?? {})) {
|
|
5434
|
+
if (value !== void 0) {
|
|
5435
|
+
merged[key] = value;
|
|
5436
|
+
}
|
|
5437
|
+
}
|
|
5438
|
+
for (const key of Object.keys(perCall ?? {})) {
|
|
5439
|
+
merged[key] = perCall[key];
|
|
5440
|
+
}
|
|
5441
|
+
return CallOptionsSchema.parse(merged);
|
|
5442
|
+
}
|
|
5443
|
+
/**
|
|
5444
|
+
* Build the extra arguments for `client.calls.create`.
|
|
5445
|
+
*
|
|
5446
|
+
* Layers, highest precedence first: this call's `callOptions`,
|
|
5447
|
+
* `VoiceChannelConfig.defaultCallOptions`, then callback URLs derived from
|
|
5448
|
+
* `voicePublicDomain` + `voiceCallEventPath`.
|
|
5449
|
+
*
|
|
5450
|
+
* A URL is derived only when its handler is registered. That's a deliberate
|
|
5451
|
+
* deviation from `websocketUrl` / `actionUrl`, which derive unconditionally:
|
|
5452
|
+
* those are load-bearing, so a wrong one fails loudly on the first call,
|
|
5453
|
+
* whereas an unwanted call-event URL fails as silent 11200 alerts for a
|
|
5454
|
+
* feature nobody asked for. Set the URLs in `defaultCallOptions` when TAC
|
|
5455
|
+
* isn't serving the routes.
|
|
5456
|
+
*/
|
|
5457
|
+
buildCallParams(callOptions) {
|
|
5458
|
+
const merged = this.mergeCallOptions(callOptions);
|
|
5459
|
+
const params = merged ? callOptionsToCreateParams(merged) : {};
|
|
5460
|
+
const wiring = [
|
|
5461
|
+
["status", "statusCallback", this.onCallStatusHandler],
|
|
5462
|
+
["amd", "asyncAmdStatusCallback", this.onAmdHandler],
|
|
5463
|
+
["recording", "recordingStatusCallback", this.onRecordingHandler]
|
|
5464
|
+
];
|
|
5465
|
+
for (const [kind, param, handler] of wiring) {
|
|
5466
|
+
if (!handler) continue;
|
|
5467
|
+
const url = this.config.callEventUrl(kind);
|
|
5468
|
+
if (url !== void 0 && params[param] === void 0) {
|
|
5469
|
+
params[param] = url;
|
|
5470
|
+
}
|
|
5471
|
+
}
|
|
5472
|
+
return params;
|
|
5473
|
+
}
|
|
5097
5474
|
/**
|
|
5098
5475
|
* Initiate an outbound voice conversation
|
|
5099
5476
|
*
|
|
@@ -5109,6 +5486,12 @@ var VoiceChannel = class _VoiceChannel extends BaseChannel {
|
|
|
5109
5486
|
* `TACConfig`, and `actionUrl` from Studio handoff (if configured), else
|
|
5110
5487
|
* derived from `TACConfig.voicePublicDomain` + `voiceActionPath`.
|
|
5111
5488
|
*
|
|
5489
|
+
* Calls-API parameters merge the same way:
|
|
5490
|
+
* 1. `options.callOptions` — per-call overrides
|
|
5491
|
+
* 2. `VoiceChannelConfig.defaultCallOptions` — channel-wide defaults
|
|
5492
|
+
* 3. Callback URLs derived from `TACConfig.voicePublicDomain` +
|
|
5493
|
+
* `voiceCallEventPath`, for handlers that are registered
|
|
5494
|
+
*
|
|
5112
5495
|
* The WebSocket URL is derived from `TACConfig.voicePublicDomain` +
|
|
5113
5496
|
* `TACConfig.voiceWebsocketPath`, unless overridden per-call via
|
|
5114
5497
|
* `options.websocketUrl`.
|
|
@@ -5124,11 +5507,17 @@ var VoiceChannel = class _VoiceChannel extends BaseChannel {
|
|
|
5124
5507
|
const merged = this.buildTwimlOptions(void 0, validated.twimlOptions);
|
|
5125
5508
|
const websocketUrl = validated.websocketUrl ?? merged.websocketUrl ?? this.resolveWebsocketUrl("initiateOutboundConversation");
|
|
5126
5509
|
const twiml = this.generateTwiml(websocketUrl, merged);
|
|
5510
|
+
const callParams = this.buildCallParams(validated.callOptions);
|
|
5511
|
+
this.logger.debug(
|
|
5512
|
+
{ twiml: redactTwimlParameters(twiml), to: maskAddress(validated.to) },
|
|
5513
|
+
"Outbound call TwiML"
|
|
5514
|
+
);
|
|
5127
5515
|
const client = this.getTwilioClient();
|
|
5128
5516
|
const call = await client.calls.create({
|
|
5129
5517
|
to: validated.to,
|
|
5130
5518
|
from: fromNumber,
|
|
5131
|
-
twiml
|
|
5519
|
+
twiml,
|
|
5520
|
+
...callParams
|
|
5132
5521
|
});
|
|
5133
5522
|
this.logger.info(
|
|
5134
5523
|
{ call_sid: call.sid, to: maskAddress(validated.to) },
|
|
@@ -5178,6 +5567,179 @@ var VoiceChannel = class _VoiceChannel extends BaseChannel {
|
|
|
5178
5567
|
return { status: 200, content: "OK", contentType: "text/plain" };
|
|
5179
5568
|
}
|
|
5180
5569
|
// =========================================================================
|
|
5570
|
+
// Call Event Handling (status callback, async AMD, recording)
|
|
5571
|
+
// =========================================================================
|
|
5572
|
+
/**
|
|
5573
|
+
* Whether a call-webhook payload belongs to the configured account.
|
|
5574
|
+
*
|
|
5575
|
+
* Twilio signature validation already gates the route; this is defense in
|
|
5576
|
+
* depth. A payload with no `AccountSid` is allowed through.
|
|
5577
|
+
*
|
|
5578
|
+
* Subaccounts: events carry the SID the call was placed on, so configure TAC
|
|
5579
|
+
* with that account or its events get dropped here.
|
|
5580
|
+
*/
|
|
5581
|
+
callEventAccountOk(form) {
|
|
5582
|
+
const accountSid = form["AccountSid"];
|
|
5583
|
+
if (accountSid && accountSid !== this.config.accountSid) {
|
|
5584
|
+
this.logger.warn(
|
|
5585
|
+
{ expected: this.config.accountSid, received: accountSid },
|
|
5586
|
+
"Call event AccountSid mismatch, ignoring"
|
|
5587
|
+
);
|
|
5588
|
+
return false;
|
|
5589
|
+
}
|
|
5590
|
+
return true;
|
|
5591
|
+
}
|
|
5592
|
+
/**
|
|
5593
|
+
* Parse a call-event webhook form and dispatch it to its handler.
|
|
5594
|
+
*
|
|
5595
|
+
* Returns 400 when the payload can't be parsed (no `CallSid`) or the handler
|
|
5596
|
+
* throws — better than handing Twilio a 200 for an event that wasn't
|
|
5597
|
+
* processed. Everything else, including no handler registered and an
|
|
5598
|
+
* account mismatch, is a 200 no-op.
|
|
5599
|
+
*/
|
|
5600
|
+
async dispatchCallEvent(kind, form, handler, parse, logFields) {
|
|
5601
|
+
const ok = { status: 200, content: "OK", contentType: "text/plain" };
|
|
5602
|
+
if (!handler || !this.callEventAccountOk(form)) {
|
|
5603
|
+
return ok;
|
|
5604
|
+
}
|
|
5605
|
+
try {
|
|
5606
|
+
const event = parse(form);
|
|
5607
|
+
this.logger.debug(logFields(event), `Call ${kind} event received`);
|
|
5608
|
+
await handler(event);
|
|
5609
|
+
} catch (error) {
|
|
5610
|
+
this.logger.error({ err: error, kind }, "Failed to process call event callback");
|
|
5611
|
+
return { status: 400, content: "Bad Request", contentType: "text/plain" };
|
|
5612
|
+
}
|
|
5613
|
+
return ok;
|
|
5614
|
+
}
|
|
5615
|
+
/**
|
|
5616
|
+
* Handle a Twilio `statusCallback` webhook.
|
|
5617
|
+
*
|
|
5618
|
+
* The developer routes the request here (`TACServer` does this automatically
|
|
5619
|
+
* for its `/status` call-event route). Parsed into a {@link CallStatusEvent}
|
|
5620
|
+
* and dispatched to the {@link onCallStatus} handler. No-op if no handler is
|
|
5621
|
+
* registered.
|
|
5622
|
+
*
|
|
5623
|
+
* @param form - Raw form data from the webhook request.
|
|
5624
|
+
*/
|
|
5625
|
+
async handleCallStatusEvent(form) {
|
|
5626
|
+
return this.dispatchCallEvent(
|
|
5627
|
+
"status",
|
|
5628
|
+
form,
|
|
5629
|
+
this.onCallStatusHandler,
|
|
5630
|
+
callStatusEventFromForm,
|
|
5631
|
+
(event) => ({ call_sid: event.callSid, call_status: event.callStatus })
|
|
5632
|
+
);
|
|
5633
|
+
}
|
|
5634
|
+
/**
|
|
5635
|
+
* Handle a Twilio `asyncAmdStatusCallback` webhook.
|
|
5636
|
+
*
|
|
5637
|
+
* The developer routes the request here (`TACServer` does this automatically
|
|
5638
|
+
* for its `/amd` call-event route). Parsed into an {@link AmdEvent} and
|
|
5639
|
+
* dispatched to the {@link onAmd} handler. No-op if no handler is registered.
|
|
5640
|
+
*
|
|
5641
|
+
* @param form - Raw form data from the webhook request.
|
|
5642
|
+
*/
|
|
5643
|
+
async handleAmdEvent(form) {
|
|
5644
|
+
return this.dispatchCallEvent("amd", form, this.onAmdHandler, amdEventFromForm, (event) => ({
|
|
5645
|
+
call_sid: event.callSid,
|
|
5646
|
+
answered_by: event.answeredBy
|
|
5647
|
+
}));
|
|
5648
|
+
}
|
|
5649
|
+
/**
|
|
5650
|
+
* Handle a Twilio `recordingStatusCallback` webhook.
|
|
5651
|
+
*
|
|
5652
|
+
* The developer routes the request here (`TACServer` does this automatically
|
|
5653
|
+
* for its `/recording` call-event route). Parsed into a
|
|
5654
|
+
* {@link RecordingEvent} and dispatched to the {@link onRecording} handler.
|
|
5655
|
+
* No-op if no handler is registered.
|
|
5656
|
+
*
|
|
5657
|
+
* @param form - Raw form data from the webhook request.
|
|
5658
|
+
*/
|
|
5659
|
+
async handleRecordingEvent(form) {
|
|
5660
|
+
return this.dispatchCallEvent(
|
|
5661
|
+
"recording",
|
|
5662
|
+
form,
|
|
5663
|
+
this.onRecordingHandler,
|
|
5664
|
+
recordingEventFromForm,
|
|
5665
|
+
(event) => ({ call_sid: event.callSid, recording_status: event.recordingStatus })
|
|
5666
|
+
);
|
|
5667
|
+
}
|
|
5668
|
+
/**
|
|
5669
|
+
* Hang up a call and clean up its ConversationRelay session.
|
|
5670
|
+
*
|
|
5671
|
+
* Works on `callSid` alone, in any mode and before a session exists, so it's
|
|
5672
|
+
* safe from a call-event handler that fires before the first prompt. Session
|
|
5673
|
+
* cleanup no-ops if no tracked session matches.
|
|
5674
|
+
*
|
|
5675
|
+
* Does not throw — hanging up an already-ended call is routine (the callee
|
|
5676
|
+
* hangs up while AMD is still resolving), and handlers shouldn't have to
|
|
5677
|
+
* guard against it.
|
|
5678
|
+
*
|
|
5679
|
+
* @param callSid - Twilio Call SID (from a call event, the outbound result, or
|
|
5680
|
+
* `ConversationSession.callSid`).
|
|
5681
|
+
* @returns True if Twilio accepted the hangup, false if it failed (logged).
|
|
5682
|
+
* Session cleanup runs either way.
|
|
5683
|
+
*/
|
|
5684
|
+
async endCall(callSid) {
|
|
5685
|
+
const client = this.getTwilioClient();
|
|
5686
|
+
let hungUp = true;
|
|
5687
|
+
try {
|
|
5688
|
+
await client.calls(callSid).update({ status: "completed" });
|
|
5689
|
+
} catch (error) {
|
|
5690
|
+
hungUp = false;
|
|
5691
|
+
this.logger.error({ err: error, call_sid: callSid }, "Failed to hang up call");
|
|
5692
|
+
}
|
|
5693
|
+
const session = this.getConversationSessionByCallSid(callSid);
|
|
5694
|
+
if (session) {
|
|
5695
|
+
await this.endConversation(session.conversationId);
|
|
5696
|
+
}
|
|
5697
|
+
return hungUp;
|
|
5698
|
+
}
|
|
5699
|
+
/**
|
|
5700
|
+
* Look up the active voice session for a Twilio Call SID.
|
|
5701
|
+
*
|
|
5702
|
+
* Out-of-band code holding a CallSid — a dashboard route, an operator action,
|
|
5703
|
+
* a call-event handler — can't reach the session-facing methods, which are
|
|
5704
|
+
* keyed by conversation id: the Orchestrator conversation id in orchestrator
|
|
5705
|
+
* mode, the CallSid only in ConversationRelay-only mode.
|
|
5706
|
+
*
|
|
5707
|
+
* Sessions are created on the caller's first prompt, not at WebSocket setup,
|
|
5708
|
+
* so this returns `undefined` for a call that connected but hasn't been spoken
|
|
5709
|
+
* into. That includes `onAmd` under `machineDetection: 'Enable'`, which fires
|
|
5710
|
+
* before the first prompt by design — hang up with {@link endCall}, which
|
|
5711
|
+
* needs no session.
|
|
5712
|
+
*
|
|
5713
|
+
* At the other end, orchestrator mode keeps the session until Conversation
|
|
5714
|
+
* Orchestrator's CLOSED webhook, so it outlives the call and `onCallStatus` /
|
|
5715
|
+
* `onRecording` do resolve. Relay-only mode tears down on the
|
|
5716
|
+
* ConversationRelay callback instead, which races them.
|
|
5717
|
+
*
|
|
5718
|
+
* @example
|
|
5719
|
+
* ```typescript
|
|
5720
|
+
* async function nudge(callSid: string): Promise<void> {
|
|
5721
|
+
* const session = voiceChannel.getConversationSessionByCallSid(callSid);
|
|
5722
|
+
* if (session) {
|
|
5723
|
+
* await voiceChannel.sendResponse(session.conversationId, 'Still there?');
|
|
5724
|
+
* }
|
|
5725
|
+
* }
|
|
5726
|
+
* ```
|
|
5727
|
+
*
|
|
5728
|
+
* @param callSid - Twilio Call SID, e.g. from
|
|
5729
|
+
* `InitiateVoiceConversationResult.callSid` or a call event.
|
|
5730
|
+
* @returns The session, or `undefined` — no first prompt yet, the call ended,
|
|
5731
|
+
* or it landed on another instance (see the horizontal-scaling note in
|
|
5732
|
+
* CLAUDE.md).
|
|
5733
|
+
*/
|
|
5734
|
+
getConversationSessionByCallSid(callSid) {
|
|
5735
|
+
for (const session of this.activeConversations.values()) {
|
|
5736
|
+
if (session.callSid === callSid) {
|
|
5737
|
+
return session;
|
|
5738
|
+
}
|
|
5739
|
+
}
|
|
5740
|
+
return void 0;
|
|
5741
|
+
}
|
|
5742
|
+
// =========================================================================
|
|
5181
5743
|
// Stream Task Management
|
|
5182
5744
|
// =========================================================================
|
|
5183
5745
|
/**
|
|
@@ -5967,6 +6529,9 @@ var DEFAULT_CONFIG = {
|
|
|
5967
6529
|
twiml: "/twiml"
|
|
5968
6530
|
}
|
|
5969
6531
|
};
|
|
6532
|
+
function stripTrailingSlash(path) {
|
|
6533
|
+
return path.replace(/\/+$/, "");
|
|
6534
|
+
}
|
|
5970
6535
|
var TACServer = class {
|
|
5971
6536
|
fastify;
|
|
5972
6537
|
tac;
|
|
@@ -5999,6 +6564,7 @@ var TACServer = class {
|
|
|
5999
6564
|
"Voice channel is configured but TACConfig.voicePublicDomain is not set. Set it directly or via the TWILIO_VOICE_PUBLIC_DOMAIN env var."
|
|
6000
6565
|
);
|
|
6001
6566
|
}
|
|
6567
|
+
this.validateCallEventPaths();
|
|
6002
6568
|
this.messagingChannels = config.messagingChannels ?? [
|
|
6003
6569
|
tac.getChannel("sms"),
|
|
6004
6570
|
tac.getChannel("rcs"),
|
|
@@ -6026,6 +6592,38 @@ var TACServer = class {
|
|
|
6026
6592
|
});
|
|
6027
6593
|
}
|
|
6028
6594
|
}
|
|
6595
|
+
/**
|
|
6596
|
+
* Validate `voiceCallEventPath`, the one path that isn't literal.
|
|
6597
|
+
*
|
|
6598
|
+
* Every other TAC path registers as configured, so a bad value is visible.
|
|
6599
|
+
* This one expands into three sub-paths, hiding a mistake the base path looks
|
|
6600
|
+
* innocent for: a sub-path colliding with another route while the base looks
|
|
6601
|
+
* unrelated (base `/hooks` vs `webhookPaths.twiml = '/hooks/status'`). Both
|
|
6602
|
+
* would register as POST routes and requests would reach the wrong handler.
|
|
6603
|
+
*
|
|
6604
|
+
* The leading-slash requirement is enforced by `TACConfigSchema` at parse
|
|
6605
|
+
* time, so it doesn't need re-checking here.
|
|
6606
|
+
*/
|
|
6607
|
+
validateCallEventPaths() {
|
|
6608
|
+
const cfg = this.tac.getConfig();
|
|
6609
|
+
const others = {
|
|
6610
|
+
[stripTrailingSlash(this.config.webhookPaths.twiml || "/twiml")]: "webhookPaths.twiml",
|
|
6611
|
+
[stripTrailingSlash(cfg.voiceActionPath)]: "TACConfig.voiceActionPath",
|
|
6612
|
+
[stripTrailingSlash(this.config.webhookPaths.conversation || "/webhook")]: "webhookPaths.conversation"
|
|
6613
|
+
};
|
|
6614
|
+
if (this.config.webhookPaths.cintel) {
|
|
6615
|
+
others[stripTrailingSlash(this.config.webhookPaths.cintel)] = "webhookPaths.cintel";
|
|
6616
|
+
}
|
|
6617
|
+
for (const kind of CALL_EVENT_KINDS) {
|
|
6618
|
+
const path = cfg.callEventPath(kind);
|
|
6619
|
+
const clash = others[stripTrailingSlash(path)];
|
|
6620
|
+
if (clash !== void 0) {
|
|
6621
|
+
throw new Error(
|
|
6622
|
+
`TACConfig.voiceCallEventPath expands to '${path}', which collides with ${clash}. Both would register as POST routes and requests would reach the wrong handler.`
|
|
6623
|
+
);
|
|
6624
|
+
}
|
|
6625
|
+
}
|
|
6626
|
+
}
|
|
6029
6627
|
getForwardedProto(request) {
|
|
6030
6628
|
const raw = request.headers["x-forwarded-proto"];
|
|
6031
6629
|
return raw?.split(",")[0]?.trim() || "https";
|
|
@@ -6150,6 +6748,34 @@ var TACServer = class {
|
|
|
6150
6748
|
}
|
|
6151
6749
|
}
|
|
6152
6750
|
);
|
|
6751
|
+
const callEventHandlers = {
|
|
6752
|
+
status: (channel, form) => channel.handleCallStatusEvent(form),
|
|
6753
|
+
amd: (channel, form) => channel.handleAmdEvent(form),
|
|
6754
|
+
recording: (channel, form) => channel.handleRecordingEvent(form)
|
|
6755
|
+
};
|
|
6756
|
+
for (const kind of CALL_EVENT_KINDS) {
|
|
6757
|
+
const handle = callEventHandlers[kind];
|
|
6758
|
+
this.fastify.post(
|
|
6759
|
+
this.tac.getConfig().callEventPath(kind),
|
|
6760
|
+
validateSignature,
|
|
6761
|
+
async (request, reply) => {
|
|
6762
|
+
try {
|
|
6763
|
+
if (!this.voiceChannel) {
|
|
6764
|
+
await reply.code(500).send({ error: "Voice channel not available" });
|
|
6765
|
+
return;
|
|
6766
|
+
}
|
|
6767
|
+
const formData = request.body ?? {};
|
|
6768
|
+
const result = await handle(this.voiceChannel, formData);
|
|
6769
|
+
await reply.code(result.status).type(result.contentType).send(result.content);
|
|
6770
|
+
} catch (error) {
|
|
6771
|
+
this.fastify.log.error(
|
|
6772
|
+
`Call ${kind} callback error: ` + (error instanceof Error ? error.message : String(error))
|
|
6773
|
+
);
|
|
6774
|
+
await reply.code(500).send({ error: "Internal server error" });
|
|
6775
|
+
}
|
|
6776
|
+
}
|
|
6777
|
+
);
|
|
6778
|
+
}
|
|
6153
6779
|
await this.fastify.register((fastify) => {
|
|
6154
6780
|
fastify.get(
|
|
6155
6781
|
this.tac.getConfig().voiceWebsocketPath,
|
|
@@ -6273,6 +6899,11 @@ var TACServer = class {
|
|
|
6273
6899
|
twiml_webhook: this.config.webhookPaths.twiml,
|
|
6274
6900
|
ws_websocket: this.tac.getConfig().voiceWebsocketPath,
|
|
6275
6901
|
conversation_relay_callback: this.tac.getConfig().voiceActionPath,
|
|
6902
|
+
...this.voiceChannel && {
|
|
6903
|
+
call_event_callbacks: CALL_EVENT_KINDS.map(
|
|
6904
|
+
(kind) => this.tac.getConfig().callEventPath(kind)
|
|
6905
|
+
)
|
|
6906
|
+
},
|
|
6276
6907
|
...this.config.webhookPaths.cintel && {
|
|
6277
6908
|
cintel_webhook: this.config.webhookPaths.cintel
|
|
6278
6909
|
}
|
|
@@ -6336,6 +6967,6 @@ var TACServer = class {
|
|
|
6336
6967
|
}
|
|
6337
6968
|
};
|
|
6338
6969
|
|
|
6339
|
-
export { ActionChannelSettingsSchema, ActionParticipantRefSchema, ActionResponseSchema, ActionTextContentSchema, AuthorInfoSchema, BaseChannel, BaseClient, BuiltInTools, CaptureRuleSchema, ChannelSettingsSchema, ChannelTypeSchema, ChatChannel, CintelParticipantSchema, CommunicationContentSchema, CommunicationParticipantSchema, CommunicationSchema, ConversationAddressSchema, ConversationClient, ConversationConfigurationSchema, ConversationGroupingTypeSchema, ConversationIntelligenceConfigSchema, ConversationParticipantSchema, ConversationRelayAttributesSchema, ConversationRelayCallbackPayloadSchema, ConversationRelayConfigSchema, ConversationResponseSchema, ConversationSessionSchema, ConversationSummaryItemSchema, CreateConversationSummariesResponseSchema, CreateObservationResponseSchema, CustomParametersSchema, EMPTY_MEMORY_RESPONSE, EnvironmentVariables, ExecutionDetailsSchema, HandoffPayloadSchema, InitiateMessagingConversationOptionsSchema, InitiateVoiceConversationOptionsSchema, IntelligenceConfigurationSchema, InterruptMessageSchema, InterruptModeSchema, JSONSchemaSchema, KnowledgeBaseSchema, KnowledgeBaseStatusSchema, KnowledgeChunkResultSchema, KnowledgeClient, KnowledgeSearchResponseSchema, LanguageAttributesSchema, LanguageConfigSchema, ListCommunicationsResponseSchema, ListConversationsResponseSchema, ListParticipantsResponseSchema, MemoryChannelTypeSchema, MemoryClient, MemoryCommunicationContentSchema, MemoryCommunicationSchema, MemoryDeliveryStatusSchema, MemoryModeSchema, MemoryParticipantSchema, MemoryParticipantTypeSchema, MemoryPromptBuilder, MemoryRetrievalRequestSchema, MemoryRetrievalResponseSchema, MessageDirectionSchema, MessagingChannel, ObservationInfoSchema, OpenAIToolSchema, OperatorProcessingResultSchema, OperatorResultEventSchema, OperatorResultProcessor, OperatorResultSchema, OperatorSchema, ParticipantAddressSchema, ParticipantAddressTypeSchema, PendingHandoffDataSchema, ProfileLookupResponseSchema, ProfileResponseSchema, PromptMessageSchema, RCSChannel, SMSChannel, SendMessageActionPayloadSchema, SendMessageActionRequestSchema, SessionInfoSchema, SessionMessageSchema, SetupMessageSchema, StatusCallbackSchema, StatusTimeoutsSchema, SummaryInfoSchema, TAC, TACChannelTypeSchema, TACCommunicationAuthorSchema, TACCommunicationContentSchema, TACCommunicationSchema, TACConfig, TACConfigSchema, TACDeliveryStatusSchema, TACMemoryResponse, TACParticipantTypeSchema, TACServer, TACTool, TextTokenMessageSchema, ToolExecutionResultSchema, TranscriptionSchema, TranscriptionWordSchema, TwiMLOptionsSchema, TwiMLRequestSchema, TwilioMemoryConfigSchema, VoiceChannel, WebSocketMessageSchema, WhatsAppChannel, buildHandoffPayload, createKnowledgeSearchTool, createKnowledgeSearchToolAsync, createKnowledgeTools, createLogger, createMemoryRetrievalTool, createMemoryTools, createMessagingTools, createSendMessageTool, createStudioHandoffTool, defineTool, isConversationId, isParticipantId, isProfileId, maskAddress, maskEmail, maskPhone, postStudioHandoff, scrubObject, scrubPii, studioExecutionsUrl, studioVoiceHandoffUrl, twiMLRequestFromForm };
|
|
6970
|
+
export { ActionChannelSettingsSchema, ActionParticipantRefSchema, ActionResponseSchema, ActionTextContentSchema, AmdEventSchema, AnthropicToolSchema, AuthorInfoSchema, BaseChannel, BaseClient, BuiltInTools, CALL_EVENT_KINDS, CallEventKindSchema, CallOptionsSchema, CallStatusEventSchema, CaptureRuleSchema, ChannelSettingsSchema, ChannelTypeSchema, ChatChannel, CintelParticipantSchema, CommunicationContentSchema, CommunicationParticipantSchema, CommunicationSchema, ConversationAddressSchema, ConversationClient, ConversationConfigurationSchema, ConversationGroupingTypeSchema, ConversationIntelligenceConfigSchema, ConversationParticipantSchema, ConversationRelayAttributesSchema, ConversationRelayCallbackPayloadSchema, ConversationRelayConfigSchema, ConversationResponseSchema, ConversationSessionSchema, ConversationSummaryItemSchema, CreateConversationSummariesResponseSchema, CreateObservationResponseSchema, CreateObservationsRequestSchema, CustomParametersSchema, EMPTY_MEMORY_RESPONSE, EnvironmentVariables, ExecutionDetailsSchema, HandoffPayloadSchema, InitiateMessagingConversationOptionsSchema, InitiateVoiceConversationOptionsSchema, IntelligenceConfigurationSchema, InterruptMessageSchema, InterruptModeSchema, JSONSchemaSchema, KnowledgeBaseSchema, KnowledgeBaseStatusSchema, KnowledgeChunkResultSchema, KnowledgeClient, KnowledgeSearchResponseSchema, LanguageAttributesSchema, LanguageConfigSchema, ListCommunicationsResponseSchema, ListConversationsResponseSchema, ListParticipantsResponseSchema, MemoryChannelTypeSchema, MemoryClient, MemoryCommunicationContentSchema, MemoryCommunicationSchema, MemoryDeliveryStatusSchema, MemoryModeSchema, MemoryParticipantSchema, MemoryParticipantTypeSchema, MemoryPromptBuilder, MemoryRetrievalRequestSchema, MemoryRetrievalResponseSchema, MessageDirectionSchema, MessagingChannel, ObservationCreateRequestSchema, ObservationInfoSchema, OpenAIToolSchema, OperatorProcessingResultSchema, OperatorResultEventSchema, OperatorResultProcessor, OperatorResultSchema, OperatorSchema, ParticipantAddressSchema, ParticipantAddressTypeSchema, PendingHandoffDataSchema, ProfileLookupResponseSchema, ProfileResponseSchema, PromptMessageSchema, RCSChannel, RecordingEventSchema, SMSChannel, SendMessageActionPayloadSchema, SendMessageActionRequestSchema, SessionInfoSchema, SessionMessageSchema, SetupMessageSchema, StatusCallbackSchema, StatusTimeoutsSchema, SummaryInfoSchema, TAC, TACChannelTypeSchema, TACCommunicationAuthorSchema, TACCommunicationContentSchema, TACCommunicationSchema, TACConfig, TACConfigSchema, TACDeliveryStatusSchema, TACMemoryResponse, TACParticipantTypeSchema, TACServer, TACTool, TextTokenMessageSchema, ToolExecutionResultSchema, TranscriptionSchema, TranscriptionWordSchema, TwiMLOptionsSchema, TwiMLRequestSchema, TwilioMemoryConfigSchema, VoiceChannel, WebSocketMessageSchema, WhatsAppChannel, amdEventFromForm, buildHandoffPayload, callOptionsToCreateParams, callStatusEventFromForm, createKnowledgeSearchTool, createKnowledgeSearchToolAsync, createKnowledgeTools, createLogger, createMemoryRetrievalTool, createMemoryTools, createMessagingTools, createSendMessageTool, createStudioHandoffTool, defineTool, isConversationId, isParticipantId, isProfileId, maskAddress, maskEmail, maskPhone, postStudioHandoff, recordingEventFromForm, redactTwimlParameters, scrubObject, scrubPii, studioExecutionsUrl, studioVoiceHandoffUrl, twiMLRequestFromForm };
|
|
6340
6971
|
//# sourceMappingURL=index.js.map
|
|
6341
6972
|
//# sourceMappingURL=index.js.map
|