twilio-agent-connect 2.0.1 → 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 +1 -0
- package/dist/index.d.ts +577 -25
- package/dist/index.js +751 -124
- package/dist/index.js.map +1 -1
- package/package.json +1 -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,14 +1544,32 @@ 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).
|
|
@@ -1427,6 +1659,13 @@ function maskAddress(address) {
|
|
|
1427
1659
|
if (address.length <= 1) return "***";
|
|
1428
1660
|
return `${address[0]}***`;
|
|
1429
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
|
+
}
|
|
1430
1669
|
|
|
1431
1670
|
// packages/core/src/lib/logger.ts
|
|
1432
1671
|
function piiLogMethod(args, method) {
|
|
@@ -1446,7 +1685,7 @@ function createLogger(options) {
|
|
|
1446
1685
|
|
|
1447
1686
|
// package.json
|
|
1448
1687
|
var package_default = {
|
|
1449
|
-
version: "2.0
|
|
1688
|
+
version: "2.1.0"};
|
|
1450
1689
|
function buildUserAgent() {
|
|
1451
1690
|
return `twilio-agent-connect-typescript/${package_default.version}`;
|
|
1452
1691
|
}
|
|
@@ -1527,7 +1766,7 @@ var BaseClient = class {
|
|
|
1527
1766
|
};
|
|
1528
1767
|
|
|
1529
1768
|
// packages/core/src/clients/memory.ts
|
|
1530
|
-
var MemoryClient = class extends BaseClient {
|
|
1769
|
+
var MemoryClient = class _MemoryClient extends BaseClient {
|
|
1531
1770
|
storeId;
|
|
1532
1771
|
constructor(config, storeId, logger) {
|
|
1533
1772
|
const baseUrl = config.region ? `https://memory.${config.region}.twilio.com` : "https://memory.twilio.com";
|
|
@@ -1580,28 +1819,46 @@ var MemoryClient = class extends BaseClient {
|
|
|
1580
1819
|
},
|
|
1581
1820
|
"Raw memory response received"
|
|
1582
1821
|
);
|
|
1583
|
-
|
|
1584
|
-
if (!validatedResponse.success) {
|
|
1822
|
+
if (data === null || typeof data !== "object") {
|
|
1585
1823
|
this.logger.warn(
|
|
1586
1824
|
{
|
|
1587
1825
|
profile_id: profileId,
|
|
1588
|
-
memory_store_id: this.storeId
|
|
1589
|
-
validation_errors: validatedResponse.error.issues
|
|
1826
|
+
memory_store_id: this.storeId
|
|
1590
1827
|
},
|
|
1591
1828
|
"Invalid memory response format"
|
|
1592
1829
|
);
|
|
1593
1830
|
return EMPTY_MEMORY_RESPONSE;
|
|
1594
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
|
+
);
|
|
1595
1851
|
this.logger.debug(
|
|
1596
1852
|
{
|
|
1597
1853
|
memory_store_id: this.storeId,
|
|
1598
1854
|
profile_id: profileId,
|
|
1599
|
-
observation_count:
|
|
1600
|
-
summary_count:
|
|
1855
|
+
observation_count: observations.length,
|
|
1856
|
+
summary_count: summaries.length,
|
|
1857
|
+
communication_count: communications.length
|
|
1601
1858
|
},
|
|
1602
1859
|
"Memory retrieval succeeded"
|
|
1603
1860
|
);
|
|
1604
|
-
return
|
|
1861
|
+
return { observations, summaries, communications };
|
|
1605
1862
|
} catch (error) {
|
|
1606
1863
|
this.logger.warn(
|
|
1607
1864
|
{
|
|
@@ -1614,6 +1871,67 @@ var MemoryClient = class extends BaseClient {
|
|
|
1614
1871
|
return EMPTY_MEMORY_RESPONSE;
|
|
1615
1872
|
}
|
|
1616
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
|
+
}
|
|
1617
1935
|
/**
|
|
1618
1936
|
* Find profiles that contain a specific identifier value
|
|
1619
1937
|
*
|
|
@@ -1701,27 +2019,30 @@ var MemoryClient = class extends BaseClient {
|
|
|
1701
2019
|
}
|
|
1702
2020
|
}
|
|
1703
2021
|
/**
|
|
1704
|
-
* 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.
|
|
1705
2027
|
*
|
|
1706
2028
|
* @param profileId - The profile ID to create the observation for
|
|
1707
2029
|
* @param content - The observation content
|
|
1708
2030
|
* @param source - Source of the observation (default: 'conversation-intelligence')
|
|
1709
2031
|
* @param conversationIds - Optional array of conversation IDs associated with this observation
|
|
1710
|
-
* @param occurredAt -
|
|
1711
|
-
* @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
|
|
1712
2034
|
*/
|
|
1713
2035
|
async createObservation(profileId, content, source = "conversation-intelligence", conversationIds, occurredAt) {
|
|
1714
2036
|
const url = `/v1/Stores/${this.storeId}/Profiles/${profileId}/Observations`;
|
|
1715
|
-
const
|
|
2037
|
+
const observation = {
|
|
1716
2038
|
content,
|
|
1717
|
-
source
|
|
2039
|
+
source,
|
|
2040
|
+
occurredAt: occurredAt && occurredAt.trim() ? occurredAt : (/* @__PURE__ */ new Date()).toISOString()
|
|
1718
2041
|
};
|
|
1719
2042
|
if (conversationIds && conversationIds.length > 0) {
|
|
1720
|
-
|
|
1721
|
-
}
|
|
1722
|
-
if (occurredAt) {
|
|
1723
|
-
requestBody.occurredAt = occurredAt;
|
|
2043
|
+
observation.conversationIds = conversationIds;
|
|
1724
2044
|
}
|
|
2045
|
+
const requestBody = { observations: [observation] };
|
|
1725
2046
|
try {
|
|
1726
2047
|
const data = await this.makeRequest(url, "POST", requestBody);
|
|
1727
2048
|
return CreateObservationResponseSchema.parse(data);
|
|
@@ -2130,22 +2451,6 @@ function generateContent(operatorResult) {
|
|
|
2130
2451
|
const jsonString = JSON.stringify(result);
|
|
2131
2452
|
return jsonString === "{}" || jsonString === "[]" ? void 0 : jsonString;
|
|
2132
2453
|
}
|
|
2133
|
-
function parseObservationsContent(jsonContent) {
|
|
2134
|
-
try {
|
|
2135
|
-
const parsed = JSON.parse(jsonContent);
|
|
2136
|
-
if (typeof parsed === "object" && parsed !== null && "observations" in parsed) {
|
|
2137
|
-
const observations = parsed.observations;
|
|
2138
|
-
if (Array.isArray(observations)) {
|
|
2139
|
-
return observations.filter(
|
|
2140
|
-
(obs) => typeof obs === "string" && obs.trim() !== ""
|
|
2141
|
-
);
|
|
2142
|
-
}
|
|
2143
|
-
}
|
|
2144
|
-
return [];
|
|
2145
|
-
} catch {
|
|
2146
|
-
return [];
|
|
2147
|
-
}
|
|
2148
|
-
}
|
|
2149
2454
|
function parseSummariesContent(jsonContent) {
|
|
2150
2455
|
try {
|
|
2151
2456
|
const parsed = JSON.parse(jsonContent);
|
|
@@ -2283,13 +2588,11 @@ var OperatorResultProcessor = class {
|
|
|
2283
2588
|
*/
|
|
2284
2589
|
async processOperatorResult(event, operatorResult) {
|
|
2285
2590
|
const operatorSid = operatorResult.operator.id;
|
|
2286
|
-
const isObservationOperator = this.config.observationOperatorSid === operatorSid;
|
|
2287
2591
|
const isSummaryOperator = this.config.summaryOperatorSid === operatorSid;
|
|
2288
|
-
if (!
|
|
2592
|
+
if (!isSummaryOperator) {
|
|
2289
2593
|
this.logger.debug(
|
|
2290
2594
|
{
|
|
2291
2595
|
operator_sid: operatorSid,
|
|
2292
|
-
observation_operator_sid: this.config.observationOperatorSid,
|
|
2293
2596
|
summary_operator_sid: this.config.summaryOperatorSid
|
|
2294
2597
|
},
|
|
2295
2598
|
"Skipping unconfigured operator"
|
|
@@ -2327,75 +2630,7 @@ var OperatorResultProcessor = class {
|
|
|
2327
2630
|
createdCount: 0
|
|
2328
2631
|
};
|
|
2329
2632
|
}
|
|
2330
|
-
|
|
2331
|
-
return this.processObservationEvent(event, operatorResult, content, profileIds);
|
|
2332
|
-
} else {
|
|
2333
|
-
return this.processSummaryEvent(event, operatorResult, content, profileIds);
|
|
2334
|
-
}
|
|
2335
|
-
}
|
|
2336
|
-
/**
|
|
2337
|
-
* Process an observation operator result
|
|
2338
|
-
*/
|
|
2339
|
-
async processObservationEvent(event, operatorResult, content, profileIds) {
|
|
2340
|
-
const observations = parseObservationsContent(content);
|
|
2341
|
-
if (observations.length === 0) {
|
|
2342
|
-
this.logger.debug(
|
|
2343
|
-
{ operator_sid: operatorResult.operator.id },
|
|
2344
|
-
"No observations found in content"
|
|
2345
|
-
);
|
|
2346
|
-
return {
|
|
2347
|
-
success: true,
|
|
2348
|
-
eventType: "observation",
|
|
2349
|
-
skipped: true,
|
|
2350
|
-
skipReason: "No observations found in operator result content",
|
|
2351
|
-
createdCount: 0
|
|
2352
|
-
};
|
|
2353
|
-
}
|
|
2354
|
-
let createdCount = 0;
|
|
2355
|
-
for (const profileId of profileIds) {
|
|
2356
|
-
for (const observation of observations) {
|
|
2357
|
-
try {
|
|
2358
|
-
await this.memoryClient.createObservation(
|
|
2359
|
-
profileId,
|
|
2360
|
-
observation,
|
|
2361
|
-
"conversation-intelligence",
|
|
2362
|
-
[event.conversationId],
|
|
2363
|
-
operatorResult.dateCreated
|
|
2364
|
-
);
|
|
2365
|
-
createdCount++;
|
|
2366
|
-
this.logger.debug(
|
|
2367
|
-
{
|
|
2368
|
-
profile_id: profileId,
|
|
2369
|
-
conversation_id: event.conversationId,
|
|
2370
|
-
observation_preview: observation.substring(0, 100)
|
|
2371
|
-
},
|
|
2372
|
-
"Created observation"
|
|
2373
|
-
);
|
|
2374
|
-
} catch (error) {
|
|
2375
|
-
this.logger.error(
|
|
2376
|
-
{
|
|
2377
|
-
err: error,
|
|
2378
|
-
profile_id: profileId,
|
|
2379
|
-
conversation_id: event.conversationId
|
|
2380
|
-
},
|
|
2381
|
-
"Failed to create observation"
|
|
2382
|
-
);
|
|
2383
|
-
return {
|
|
2384
|
-
success: false,
|
|
2385
|
-
eventType: "observation",
|
|
2386
|
-
skipped: false,
|
|
2387
|
-
error: `Failed to create observation: ${error instanceof Error ? error.message : String(error)}`,
|
|
2388
|
-
createdCount
|
|
2389
|
-
};
|
|
2390
|
-
}
|
|
2391
|
-
}
|
|
2392
|
-
}
|
|
2393
|
-
return {
|
|
2394
|
-
success: true,
|
|
2395
|
-
eventType: "observation",
|
|
2396
|
-
skipped: false,
|
|
2397
|
-
createdCount
|
|
2398
|
-
};
|
|
2633
|
+
return this.processSummaryEvent(event, operatorResult, content, profileIds);
|
|
2399
2634
|
}
|
|
2400
2635
|
/**
|
|
2401
2636
|
* Process a summary operator result
|
|
@@ -2517,7 +2752,6 @@ var TAC = class _TAC {
|
|
|
2517
2752
|
tac.memoryClient,
|
|
2518
2753
|
{
|
|
2519
2754
|
configurationId: tac.config.cintelConfigurationId,
|
|
2520
|
-
observationOperatorSid: tac.config.cintelObservationOperatorSid,
|
|
2521
2755
|
summaryOperatorSid: tac.config.cintelSummaryOperatorSid
|
|
2522
2756
|
},
|
|
2523
2757
|
tac.logger.child({ component: "cintel" })
|
|
@@ -3502,10 +3736,16 @@ var MessagingChannel = class extends BaseChannel {
|
|
|
3502
3736
|
if (!session.aiAgentInfo) {
|
|
3503
3737
|
const resolved = await this.reconcileParticipants(conversationId);
|
|
3504
3738
|
if (!resolved) {
|
|
3505
|
-
|
|
3506
|
-
|
|
3507
|
-
"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.`
|
|
3508
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
|
+
});
|
|
3509
3749
|
return;
|
|
3510
3750
|
}
|
|
3511
3751
|
const [agentParticipant, customerParticipant] = resolved;
|
|
@@ -4450,6 +4690,9 @@ var VoiceChannel = class _VoiceChannel extends BaseChannel {
|
|
|
4450
4690
|
twilioClient;
|
|
4451
4691
|
voiceConfig;
|
|
4452
4692
|
onInboundCallTwimlHandler;
|
|
4693
|
+
onCallStatusHandler;
|
|
4694
|
+
onAmdHandler;
|
|
4695
|
+
onRecordingHandler;
|
|
4453
4696
|
constructor(tac, options) {
|
|
4454
4697
|
super(tac, options);
|
|
4455
4698
|
this.voiceConfig = options ?? {};
|
|
@@ -4485,6 +4728,74 @@ var VoiceChannel = class _VoiceChannel extends BaseChannel {
|
|
|
4485
4728
|
onInboundCallTwiml(callback) {
|
|
4486
4729
|
this.onInboundCallTwimlHandler = callback;
|
|
4487
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
|
+
}
|
|
4488
4799
|
/**
|
|
4489
4800
|
* Resolve the public WebSocket URL from `TACConfig.voicePublicDomain` +
|
|
4490
4801
|
* `TACConfig.voiceWebsocketPath`. Throws if `voicePublicDomain` isn't set.
|
|
@@ -4675,6 +4986,7 @@ var VoiceChannel = class _VoiceChannel extends BaseChannel {
|
|
|
4675
4986
|
this.webSocketConnections.set(conversationId, ws);
|
|
4676
4987
|
this.callSidToConversationId.set(callSid, conversationId);
|
|
4677
4988
|
const session = this.startConversation(conversationId);
|
|
4989
|
+
session.callSid = callSid;
|
|
4678
4990
|
if (fromNumber) {
|
|
4679
4991
|
session.authorInfo = { address: fromNumber };
|
|
4680
4992
|
}
|
|
@@ -4712,6 +5024,7 @@ var VoiceChannel = class _VoiceChannel extends BaseChannel {
|
|
|
4712
5024
|
this.webSocketConnections.set(conversationId, ws);
|
|
4713
5025
|
this.callSidToConversationId.set(callSid, conversationId);
|
|
4714
5026
|
const session = this.startConversation(conversationId, profileId);
|
|
5027
|
+
session.callSid = callSid;
|
|
4715
5028
|
if (customerAddress) {
|
|
4716
5029
|
session.authorInfo = {
|
|
4717
5030
|
address: customerAddress
|
|
@@ -5098,6 +5411,66 @@ var VoiceChannel = class _VoiceChannel extends BaseChannel {
|
|
|
5098
5411
|
// =========================================================================
|
|
5099
5412
|
// Outbound Call Handling
|
|
5100
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
|
+
}
|
|
5101
5474
|
/**
|
|
5102
5475
|
* Initiate an outbound voice conversation
|
|
5103
5476
|
*
|
|
@@ -5113,6 +5486,12 @@ var VoiceChannel = class _VoiceChannel extends BaseChannel {
|
|
|
5113
5486
|
* `TACConfig`, and `actionUrl` from Studio handoff (if configured), else
|
|
5114
5487
|
* derived from `TACConfig.voicePublicDomain` + `voiceActionPath`.
|
|
5115
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
|
+
*
|
|
5116
5495
|
* The WebSocket URL is derived from `TACConfig.voicePublicDomain` +
|
|
5117
5496
|
* `TACConfig.voiceWebsocketPath`, unless overridden per-call via
|
|
5118
5497
|
* `options.websocketUrl`.
|
|
@@ -5128,11 +5507,17 @@ var VoiceChannel = class _VoiceChannel extends BaseChannel {
|
|
|
5128
5507
|
const merged = this.buildTwimlOptions(void 0, validated.twimlOptions);
|
|
5129
5508
|
const websocketUrl = validated.websocketUrl ?? merged.websocketUrl ?? this.resolveWebsocketUrl("initiateOutboundConversation");
|
|
5130
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
|
+
);
|
|
5131
5515
|
const client = this.getTwilioClient();
|
|
5132
5516
|
const call = await client.calls.create({
|
|
5133
5517
|
to: validated.to,
|
|
5134
5518
|
from: fromNumber,
|
|
5135
|
-
twiml
|
|
5519
|
+
twiml,
|
|
5520
|
+
...callParams
|
|
5136
5521
|
});
|
|
5137
5522
|
this.logger.info(
|
|
5138
5523
|
{ call_sid: call.sid, to: maskAddress(validated.to) },
|
|
@@ -5182,6 +5567,179 @@ var VoiceChannel = class _VoiceChannel extends BaseChannel {
|
|
|
5182
5567
|
return { status: 200, content: "OK", contentType: "text/plain" };
|
|
5183
5568
|
}
|
|
5184
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
|
+
// =========================================================================
|
|
5185
5743
|
// Stream Task Management
|
|
5186
5744
|
// =========================================================================
|
|
5187
5745
|
/**
|
|
@@ -5971,6 +6529,9 @@ var DEFAULT_CONFIG = {
|
|
|
5971
6529
|
twiml: "/twiml"
|
|
5972
6530
|
}
|
|
5973
6531
|
};
|
|
6532
|
+
function stripTrailingSlash(path) {
|
|
6533
|
+
return path.replace(/\/+$/, "");
|
|
6534
|
+
}
|
|
5974
6535
|
var TACServer = class {
|
|
5975
6536
|
fastify;
|
|
5976
6537
|
tac;
|
|
@@ -6003,6 +6564,7 @@ var TACServer = class {
|
|
|
6003
6564
|
"Voice channel is configured but TACConfig.voicePublicDomain is not set. Set it directly or via the TWILIO_VOICE_PUBLIC_DOMAIN env var."
|
|
6004
6565
|
);
|
|
6005
6566
|
}
|
|
6567
|
+
this.validateCallEventPaths();
|
|
6006
6568
|
this.messagingChannels = config.messagingChannels ?? [
|
|
6007
6569
|
tac.getChannel("sms"),
|
|
6008
6570
|
tac.getChannel("rcs"),
|
|
@@ -6030,6 +6592,38 @@ var TACServer = class {
|
|
|
6030
6592
|
});
|
|
6031
6593
|
}
|
|
6032
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
|
+
}
|
|
6033
6627
|
getForwardedProto(request) {
|
|
6034
6628
|
const raw = request.headers["x-forwarded-proto"];
|
|
6035
6629
|
return raw?.split(",")[0]?.trim() || "https";
|
|
@@ -6154,6 +6748,34 @@ var TACServer = class {
|
|
|
6154
6748
|
}
|
|
6155
6749
|
}
|
|
6156
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
|
+
}
|
|
6157
6779
|
await this.fastify.register((fastify) => {
|
|
6158
6780
|
fastify.get(
|
|
6159
6781
|
this.tac.getConfig().voiceWebsocketPath,
|
|
@@ -6277,6 +6899,11 @@ var TACServer = class {
|
|
|
6277
6899
|
twiml_webhook: this.config.webhookPaths.twiml,
|
|
6278
6900
|
ws_websocket: this.tac.getConfig().voiceWebsocketPath,
|
|
6279
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
|
+
},
|
|
6280
6907
|
...this.config.webhookPaths.cintel && {
|
|
6281
6908
|
cintel_webhook: this.config.webhookPaths.cintel
|
|
6282
6909
|
}
|
|
@@ -6340,6 +6967,6 @@ var TACServer = class {
|
|
|
6340
6967
|
}
|
|
6341
6968
|
};
|
|
6342
6969
|
|
|
6343
|
-
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 };
|
|
6344
6971
|
//# sourceMappingURL=index.js.map
|
|
6345
6972
|
//# sourceMappingURL=index.js.map
|