@kognitivedev/telephony 0.2.29
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/.turbo/turbo-build.log +2 -0
- package/.turbo/turbo-test.log +13 -0
- package/CHANGELOG.md +10 -0
- package/README.md +98 -0
- package/dist/audio.d.ts +4 -0
- package/dist/audio.js +55 -0
- package/dist/index.d.ts +6 -0
- package/dist/index.js +24 -0
- package/dist/registry.d.ts +15 -0
- package/dist/registry.js +45 -0
- package/dist/rtp.d.ts +28 -0
- package/dist/rtp.js +88 -0
- package/dist/twilio.d.ts +24 -0
- package/dist/twilio.js +136 -0
- package/dist/types.d.ts +242 -0
- package/dist/types.js +2 -0
- package/package.json +56 -0
- package/src/__tests__/audio.test.ts +58 -0
- package/src/__tests__/registry.test.ts +67 -0
- package/src/__tests__/rtp.test.ts +42 -0
- package/src/__tests__/twilio.test.ts +110 -0
- package/src/audio.ts +63 -0
- package/src/index.ts +22 -0
- package/src/registry.ts +56 -0
- package/src/rtp.ts +107 -0
- package/src/twilio.ts +208 -0
- package/src/types.ts +326 -0
- package/tsconfig.json +18 -0
package/src/registry.ts
ADDED
|
@@ -0,0 +1,56 @@
|
|
|
1
|
+
import type { TelephonyCallControlAdapter, TelephonyProvider, TelephonyProviderAdapter } from "./types";
|
|
2
|
+
|
|
3
|
+
export interface TelephonyAdapterRegistry {
|
|
4
|
+
registerProvider(adapter: TelephonyProviderAdapter): void;
|
|
5
|
+
registerCallControl(adapter: TelephonyCallControlAdapter): void;
|
|
6
|
+
getProvider(provider: TelephonyProvider): TelephonyProviderAdapter | undefined;
|
|
7
|
+
getCallControl(provider: TelephonyProvider): TelephonyCallControlAdapter | undefined;
|
|
8
|
+
requireProvider(provider: TelephonyProvider): TelephonyProviderAdapter;
|
|
9
|
+
requireCallControl(provider: TelephonyProvider): TelephonyCallControlAdapter;
|
|
10
|
+
listProviders(): TelephonyProvider[];
|
|
11
|
+
listCallControlProviders(): TelephonyProvider[];
|
|
12
|
+
}
|
|
13
|
+
|
|
14
|
+
export function createTelephonyAdapterRegistry(input: {
|
|
15
|
+
providers?: TelephonyProviderAdapter[];
|
|
16
|
+
callControls?: TelephonyCallControlAdapter[];
|
|
17
|
+
} = {}): TelephonyAdapterRegistry {
|
|
18
|
+
const providers = new Map<TelephonyProvider, TelephonyProviderAdapter>();
|
|
19
|
+
const callControls = new Map<TelephonyProvider, TelephonyCallControlAdapter>();
|
|
20
|
+
|
|
21
|
+
const registry: TelephonyAdapterRegistry = {
|
|
22
|
+
registerProvider(adapter) {
|
|
23
|
+
providers.set(adapter.provider, adapter);
|
|
24
|
+
},
|
|
25
|
+
registerCallControl(adapter) {
|
|
26
|
+
callControls.set(adapter.provider, adapter);
|
|
27
|
+
},
|
|
28
|
+
getProvider(provider) {
|
|
29
|
+
return providers.get(provider);
|
|
30
|
+
},
|
|
31
|
+
getCallControl(provider) {
|
|
32
|
+
return callControls.get(provider);
|
|
33
|
+
},
|
|
34
|
+
requireProvider(provider) {
|
|
35
|
+
const adapter = providers.get(provider);
|
|
36
|
+
if (!adapter) throw new Error(`Telephony provider "${provider}" is not registered`);
|
|
37
|
+
return adapter;
|
|
38
|
+
},
|
|
39
|
+
requireCallControl(provider) {
|
|
40
|
+
const adapter = callControls.get(provider);
|
|
41
|
+
if (!adapter) throw new Error(`Telephony call-control provider "${provider}" is not registered`);
|
|
42
|
+
return adapter;
|
|
43
|
+
},
|
|
44
|
+
listProviders() {
|
|
45
|
+
return [...providers.keys()];
|
|
46
|
+
},
|
|
47
|
+
listCallControlProviders() {
|
|
48
|
+
return [...callControls.keys()];
|
|
49
|
+
},
|
|
50
|
+
};
|
|
51
|
+
|
|
52
|
+
for (const adapter of input.providers ?? []) registry.registerProvider(adapter);
|
|
53
|
+
for (const adapter of input.callControls ?? []) registry.registerCallControl(adapter);
|
|
54
|
+
|
|
55
|
+
return registry;
|
|
56
|
+
}
|
package/src/rtp.ts
ADDED
|
@@ -0,0 +1,107 @@
|
|
|
1
|
+
export const RTP_VERSION = 2;
|
|
2
|
+
export const RTP_PAYLOAD_TYPE_PCMU = 0;
|
|
3
|
+
export const RTP_PAYLOAD_TYPE_PCMA = 8;
|
|
4
|
+
|
|
5
|
+
export interface RtpPacket {
|
|
6
|
+
version: number;
|
|
7
|
+
padding: boolean;
|
|
8
|
+
extension: boolean;
|
|
9
|
+
marker: boolean;
|
|
10
|
+
payloadType: number;
|
|
11
|
+
sequenceNumber: number;
|
|
12
|
+
timestamp: number;
|
|
13
|
+
ssrc: number;
|
|
14
|
+
csrc: number[];
|
|
15
|
+
payload: Buffer;
|
|
16
|
+
}
|
|
17
|
+
|
|
18
|
+
export interface SerializeRtpPacketInput {
|
|
19
|
+
marker?: boolean;
|
|
20
|
+
payloadType?: number;
|
|
21
|
+
sequenceNumber: number;
|
|
22
|
+
timestamp: number;
|
|
23
|
+
ssrc: number;
|
|
24
|
+
payload: Buffer | Uint8Array;
|
|
25
|
+
csrc?: number[];
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
function assertUint16(value: number, label: string) {
|
|
29
|
+
if (!Number.isInteger(value) || value < 0 || value > 0xffff) {
|
|
30
|
+
throw new Error(`${label} must be a uint16`);
|
|
31
|
+
}
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
function assertUint32(value: number, label: string) {
|
|
35
|
+
if (!Number.isInteger(value) || value < 0 || value > 0xffffffff) {
|
|
36
|
+
throw new Error(`${label} must be a uint32`);
|
|
37
|
+
}
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
export function nextRtpSequenceNumber(value: number) {
|
|
41
|
+
return (value + 1) & 0xffff;
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
export function nextRtpTimestamp(value: number, increment: number) {
|
|
45
|
+
assertUint32(value, "timestamp");
|
|
46
|
+
if (!Number.isInteger(increment) || increment < 0) throw new Error("increment must be a non-negative integer");
|
|
47
|
+
return (value + increment) >>> 0;
|
|
48
|
+
}
|
|
49
|
+
|
|
50
|
+
export function parseRtpPacket(buffer: Buffer | Uint8Array): RtpPacket {
|
|
51
|
+
const data = Buffer.isBuffer(buffer) ? buffer : Buffer.from(buffer);
|
|
52
|
+
if (data.length < 12) throw new Error("RTP packet is too short");
|
|
53
|
+
|
|
54
|
+
const first = data[0] ?? 0;
|
|
55
|
+
const version = first >> 6;
|
|
56
|
+
if (version !== RTP_VERSION) throw new Error(`Unsupported RTP version ${version}`);
|
|
57
|
+
|
|
58
|
+
const csrcCount = first & 0x0f;
|
|
59
|
+
const headerLength = 12 + csrcCount * 4;
|
|
60
|
+
if (data.length < headerLength) throw new Error("RTP packet has truncated CSRC list");
|
|
61
|
+
|
|
62
|
+
const second = data[1] ?? 0;
|
|
63
|
+
const csrc: number[] = [];
|
|
64
|
+
for (let offset = 12; offset < headerLength; offset += 4) {
|
|
65
|
+
csrc.push(data.readUInt32BE(offset));
|
|
66
|
+
}
|
|
67
|
+
|
|
68
|
+
return {
|
|
69
|
+
version,
|
|
70
|
+
padding: Boolean(first & 0x20),
|
|
71
|
+
extension: Boolean(first & 0x10),
|
|
72
|
+
marker: Boolean(second & 0x80),
|
|
73
|
+
payloadType: second & 0x7f,
|
|
74
|
+
sequenceNumber: data.readUInt16BE(2),
|
|
75
|
+
timestamp: data.readUInt32BE(4),
|
|
76
|
+
ssrc: data.readUInt32BE(8),
|
|
77
|
+
csrc,
|
|
78
|
+
payload: data.subarray(headerLength),
|
|
79
|
+
};
|
|
80
|
+
}
|
|
81
|
+
|
|
82
|
+
export function serializeRtpPacket(input: SerializeRtpPacketInput): Buffer {
|
|
83
|
+
assertUint16(input.sequenceNumber, "sequenceNumber");
|
|
84
|
+
assertUint32(input.timestamp, "timestamp");
|
|
85
|
+
assertUint32(input.ssrc, "ssrc");
|
|
86
|
+
const payloadType = input.payloadType ?? RTP_PAYLOAD_TYPE_PCMU;
|
|
87
|
+
if (!Number.isInteger(payloadType) || payloadType < 0 || payloadType > 0x7f) {
|
|
88
|
+
throw new Error("payloadType must be a 7-bit integer");
|
|
89
|
+
}
|
|
90
|
+
const csrc = input.csrc ?? [];
|
|
91
|
+
if (csrc.length > 15) throw new Error("RTP packets can contain at most 15 CSRC entries");
|
|
92
|
+
for (const [index, value] of csrc.entries()) assertUint32(value, `csrc[${index}]`);
|
|
93
|
+
|
|
94
|
+
const payload = Buffer.isBuffer(input.payload) ? input.payload : Buffer.from(input.payload);
|
|
95
|
+
const headerLength = 12 + csrc.length * 4;
|
|
96
|
+
const packet = Buffer.alloc(headerLength + payload.length);
|
|
97
|
+
packet[0] = (RTP_VERSION << 6) | csrc.length;
|
|
98
|
+
packet[1] = (input.marker ? 0x80 : 0) | payloadType;
|
|
99
|
+
packet.writeUInt16BE(input.sequenceNumber, 2);
|
|
100
|
+
packet.writeUInt32BE(input.timestamp >>> 0, 4);
|
|
101
|
+
packet.writeUInt32BE(input.ssrc >>> 0, 8);
|
|
102
|
+
for (let index = 0; index < csrc.length; index += 1) {
|
|
103
|
+
packet.writeUInt32BE(csrc[index] ?? 0, 12 + index * 4);
|
|
104
|
+
}
|
|
105
|
+
payload.copy(packet, headerLength);
|
|
106
|
+
return packet;
|
|
107
|
+
}
|
package/src/twilio.ts
ADDED
|
@@ -0,0 +1,208 @@
|
|
|
1
|
+
import {
|
|
2
|
+
computeTwilioBodySha256,
|
|
3
|
+
createTwilioInboundCallResponse,
|
|
4
|
+
createTwilioMediaStreamClearMessage,
|
|
5
|
+
createTwilioMediaStreamMarkMessage,
|
|
6
|
+
createTwilioMediaStreamMediaMessage,
|
|
7
|
+
createTwilioMediaStreamTwiml,
|
|
8
|
+
decodeTwilioMulawBase64ToFloat32,
|
|
9
|
+
decodeTwilioMulawBase64ToPcm16,
|
|
10
|
+
encodeFloat32ToTwilioMulawBase64,
|
|
11
|
+
encodePcm16ToTwilioMulawBase64,
|
|
12
|
+
handleTwilioMediaStreamMessage,
|
|
13
|
+
parseTwilioMediaStreamMessage,
|
|
14
|
+
serializeTwilioMediaStreamMessage,
|
|
15
|
+
validateTwilioRequestSignature,
|
|
16
|
+
type CreateTwilioMediaStreamTwimlOptions,
|
|
17
|
+
type TwilioInboundCallContext,
|
|
18
|
+
type TwilioInboundCallResponse,
|
|
19
|
+
type TwilioInboundCallResponseConfig,
|
|
20
|
+
type TwilioInboundMediaStreamMessage,
|
|
21
|
+
type TwilioOutboundMediaStreamMessage,
|
|
22
|
+
type TwilioRequestSignatureValidationOptions,
|
|
23
|
+
} from "@kognitivedev/voice/telephony";
|
|
24
|
+
import type {
|
|
25
|
+
TelephonyCallControlAdapter,
|
|
26
|
+
TelephonyCallControlResult,
|
|
27
|
+
TelephonyOutboundCallInput,
|
|
28
|
+
TelephonyOutboundCallResult,
|
|
29
|
+
TelephonyProviderAdapter,
|
|
30
|
+
} from "./types";
|
|
31
|
+
|
|
32
|
+
export {
|
|
33
|
+
computeTwilioBodySha256,
|
|
34
|
+
createTwilioInboundCallResponse,
|
|
35
|
+
createTwilioMediaStreamClearMessage,
|
|
36
|
+
createTwilioMediaStreamMarkMessage,
|
|
37
|
+
createTwilioMediaStreamMediaMessage,
|
|
38
|
+
createTwilioMediaStreamTwiml,
|
|
39
|
+
decodeTwilioMulawBase64ToFloat32,
|
|
40
|
+
decodeTwilioMulawBase64ToPcm16,
|
|
41
|
+
encodeFloat32ToTwilioMulawBase64,
|
|
42
|
+
encodePcm16ToTwilioMulawBase64,
|
|
43
|
+
handleTwilioMediaStreamMessage,
|
|
44
|
+
parseTwilioMediaStreamMessage,
|
|
45
|
+
serializeTwilioMediaStreamMessage,
|
|
46
|
+
validateTwilioRequestSignature,
|
|
47
|
+
};
|
|
48
|
+
|
|
49
|
+
export type {
|
|
50
|
+
CreateTwilioMediaStreamTwimlOptions,
|
|
51
|
+
TwilioInboundCallContext,
|
|
52
|
+
TwilioInboundCallResponse,
|
|
53
|
+
TwilioInboundCallResponseConfig,
|
|
54
|
+
TwilioInboundMediaStreamMessage,
|
|
55
|
+
TwilioOutboundMediaStreamMessage,
|
|
56
|
+
TwilioRequestSignatureValidationOptions,
|
|
57
|
+
};
|
|
58
|
+
|
|
59
|
+
export interface TwilioOutboundCallOptions extends TelephonyOutboundCallInput {
|
|
60
|
+
accountSid: string;
|
|
61
|
+
authToken: string;
|
|
62
|
+
fetch?: typeof fetch;
|
|
63
|
+
}
|
|
64
|
+
|
|
65
|
+
export interface TwilioTelephonyProviderConfig {
|
|
66
|
+
accountSid: string;
|
|
67
|
+
authToken: string;
|
|
68
|
+
fetch?: typeof fetch;
|
|
69
|
+
}
|
|
70
|
+
|
|
71
|
+
export interface TwilioCompleteCallOptions {
|
|
72
|
+
accountSid: string;
|
|
73
|
+
authToken: string;
|
|
74
|
+
providerCallId: string;
|
|
75
|
+
fetch?: typeof fetch;
|
|
76
|
+
}
|
|
77
|
+
|
|
78
|
+
function normalizeTwilioStatus(value: unknown): TelephonyOutboundCallResult["status"] {
|
|
79
|
+
switch (value) {
|
|
80
|
+
case "queued":
|
|
81
|
+
case "ringing":
|
|
82
|
+
case "in-progress":
|
|
83
|
+
return value === "in-progress" ? "active" : value;
|
|
84
|
+
case "completed":
|
|
85
|
+
case "failed":
|
|
86
|
+
case "busy":
|
|
87
|
+
case "no-answer":
|
|
88
|
+
case "cancelled":
|
|
89
|
+
return value;
|
|
90
|
+
default:
|
|
91
|
+
return "unknown";
|
|
92
|
+
}
|
|
93
|
+
}
|
|
94
|
+
|
|
95
|
+
function assertTwilioValue(value: string | undefined, label: string) {
|
|
96
|
+
if (!value?.trim()) throw new Error(`${label} is required`);
|
|
97
|
+
return value.trim();
|
|
98
|
+
}
|
|
99
|
+
|
|
100
|
+
export async function createTwilioOutboundCall(options: TwilioOutboundCallOptions): Promise<TelephonyOutboundCallResult> {
|
|
101
|
+
const accountSid = assertTwilioValue(options.accountSid, "Twilio accountSid");
|
|
102
|
+
const authToken = assertTwilioValue(options.authToken, "Twilio authToken");
|
|
103
|
+
const from = assertTwilioValue(options.from, "from");
|
|
104
|
+
const to = assertTwilioValue(options.to, "to");
|
|
105
|
+
const answerUrl = assertTwilioValue(options.answerUrl, "answerUrl");
|
|
106
|
+
const fetchImpl = options.fetch ?? globalThis.fetch.bind(globalThis);
|
|
107
|
+
const form = new URLSearchParams({
|
|
108
|
+
From: from,
|
|
109
|
+
To: to,
|
|
110
|
+
Url: answerUrl,
|
|
111
|
+
});
|
|
112
|
+
if (options.statusCallbackUrl) {
|
|
113
|
+
form.set("StatusCallback", options.statusCallbackUrl);
|
|
114
|
+
for (const event of ["initiated", "ringing", "answered", "completed"]) {
|
|
115
|
+
form.append("StatusCallbackEvent", event);
|
|
116
|
+
}
|
|
117
|
+
}
|
|
118
|
+
|
|
119
|
+
const response = await fetchImpl(`https://api.twilio.com/2010-04-01/Accounts/${encodeURIComponent(accountSid)}/Calls.json`, {
|
|
120
|
+
method: "POST",
|
|
121
|
+
headers: {
|
|
122
|
+
Authorization: `Basic ${Buffer.from(`${accountSid}:${authToken}`).toString("base64")}`,
|
|
123
|
+
"Content-Type": "application/x-www-form-urlencoded",
|
|
124
|
+
},
|
|
125
|
+
body: form,
|
|
126
|
+
});
|
|
127
|
+
|
|
128
|
+
const raw = await response.json().catch(() => ({})) as Record<string, unknown>;
|
|
129
|
+
if (!response.ok) {
|
|
130
|
+
const message = typeof raw.message === "string" ? raw.message : response.statusText;
|
|
131
|
+
throw new Error(`Twilio outbound call failed: ${message}`);
|
|
132
|
+
}
|
|
133
|
+
|
|
134
|
+
const providerCallId = typeof raw.sid === "string" ? raw.sid : options.providerCallId;
|
|
135
|
+
if (!providerCallId) {
|
|
136
|
+
throw new Error("Twilio outbound call response did not include sid");
|
|
137
|
+
}
|
|
138
|
+
|
|
139
|
+
return {
|
|
140
|
+
provider: "twilio",
|
|
141
|
+
providerCallId,
|
|
142
|
+
status: normalizeTwilioStatus(raw.status),
|
|
143
|
+
from,
|
|
144
|
+
to,
|
|
145
|
+
raw,
|
|
146
|
+
};
|
|
147
|
+
}
|
|
148
|
+
|
|
149
|
+
export async function completeTwilioCall(options: TwilioCompleteCallOptions): Promise<TelephonyCallControlResult> {
|
|
150
|
+
const accountSid = assertTwilioValue(options.accountSid, "Twilio accountSid");
|
|
151
|
+
const authToken = assertTwilioValue(options.authToken, "Twilio authToken");
|
|
152
|
+
const providerCallId = assertTwilioValue(options.providerCallId, "Twilio providerCallId");
|
|
153
|
+
const fetchImpl = options.fetch ?? globalThis.fetch.bind(globalThis);
|
|
154
|
+
const form = new URLSearchParams({ Status: "completed" });
|
|
155
|
+
|
|
156
|
+
const response = await fetchImpl(
|
|
157
|
+
`https://api.twilio.com/2010-04-01/Accounts/${encodeURIComponent(accountSid)}/Calls/${encodeURIComponent(providerCallId)}.json`,
|
|
158
|
+
{
|
|
159
|
+
method: "POST",
|
|
160
|
+
headers: {
|
|
161
|
+
Authorization: `Basic ${Buffer.from(`${accountSid}:${authToken}`).toString("base64")}`,
|
|
162
|
+
"Content-Type": "application/x-www-form-urlencoded",
|
|
163
|
+
},
|
|
164
|
+
body: form,
|
|
165
|
+
},
|
|
166
|
+
);
|
|
167
|
+
|
|
168
|
+
const raw = await response.json().catch(() => ({})) as Record<string, unknown>;
|
|
169
|
+
if (!response.ok) {
|
|
170
|
+
const message = typeof raw.message === "string" ? raw.message : response.statusText;
|
|
171
|
+
throw new Error(`Twilio call completion failed: ${message}`);
|
|
172
|
+
}
|
|
173
|
+
|
|
174
|
+
return {
|
|
175
|
+
provider: "twilio",
|
|
176
|
+
providerCallId,
|
|
177
|
+
status: normalizeTwilioStatus(raw.status ?? "completed"),
|
|
178
|
+
raw,
|
|
179
|
+
};
|
|
180
|
+
}
|
|
181
|
+
|
|
182
|
+
export function createTwilioTelephonyProvider(config: TwilioTelephonyProviderConfig): TelephonyProviderAdapter {
|
|
183
|
+
return {
|
|
184
|
+
provider: "twilio",
|
|
185
|
+
createOutboundCall(input) {
|
|
186
|
+
return createTwilioOutboundCall({
|
|
187
|
+
...input,
|
|
188
|
+
accountSid: config.accountSid,
|
|
189
|
+
authToken: config.authToken,
|
|
190
|
+
fetch: config.fetch,
|
|
191
|
+
});
|
|
192
|
+
},
|
|
193
|
+
};
|
|
194
|
+
}
|
|
195
|
+
|
|
196
|
+
export function createTwilioPhoneControlAdapter(config: TwilioTelephonyProviderConfig): TelephonyCallControlAdapter {
|
|
197
|
+
return {
|
|
198
|
+
provider: "twilio",
|
|
199
|
+
hangUpCall(input) {
|
|
200
|
+
return completeTwilioCall({
|
|
201
|
+
accountSid: config.accountSid,
|
|
202
|
+
authToken: config.authToken,
|
|
203
|
+
providerCallId: input.providerCallId,
|
|
204
|
+
fetch: config.fetch,
|
|
205
|
+
});
|
|
206
|
+
},
|
|
207
|
+
};
|
|
208
|
+
}
|
package/src/types.ts
ADDED
|
@@ -0,0 +1,326 @@
|
|
|
1
|
+
export type TelephonyProvider =
|
|
2
|
+
| "twilio"
|
|
3
|
+
| "telnyx"
|
|
4
|
+
| "cm_com"
|
|
5
|
+
| "didww"
|
|
6
|
+
| "sinch"
|
|
7
|
+
| "bird"
|
|
8
|
+
| "sip_custom";
|
|
9
|
+
|
|
10
|
+
export type TelephonyConnectionMode =
|
|
11
|
+
| "provider_managed"
|
|
12
|
+
| "byoc_trunk"
|
|
13
|
+
| "sip_uri"
|
|
14
|
+
| "pbx_extension";
|
|
15
|
+
|
|
16
|
+
export type TelephonyTransport = "udp" | "tcp" | "tls";
|
|
17
|
+
|
|
18
|
+
export type TelephonyMediaEncryption = "none" | "srtp";
|
|
19
|
+
|
|
20
|
+
export type TelephonyRegion =
|
|
21
|
+
| "eu"
|
|
22
|
+
| "de"
|
|
23
|
+
| "nl"
|
|
24
|
+
| "uk"
|
|
25
|
+
| "fr"
|
|
26
|
+
| "tr"
|
|
27
|
+
| "us"
|
|
28
|
+
| "global"
|
|
29
|
+
| string;
|
|
30
|
+
|
|
31
|
+
export type TelephonyCallDirection = "inbound" | "outbound";
|
|
32
|
+
|
|
33
|
+
export type TelephonyCallStatus =
|
|
34
|
+
| "queued"
|
|
35
|
+
| "ringing"
|
|
36
|
+
| "created"
|
|
37
|
+
| "active"
|
|
38
|
+
| "completed"
|
|
39
|
+
| "failed"
|
|
40
|
+
| "busy"
|
|
41
|
+
| "no-answer"
|
|
42
|
+
| "cancelled"
|
|
43
|
+
| "unknown";
|
|
44
|
+
|
|
45
|
+
export interface TelephonyNumberCapabilities {
|
|
46
|
+
voice?: boolean;
|
|
47
|
+
sms?: boolean;
|
|
48
|
+
mms?: boolean;
|
|
49
|
+
sip?: boolean;
|
|
50
|
+
}
|
|
51
|
+
|
|
52
|
+
export interface TelephonyProviderCapabilities {
|
|
53
|
+
inboundPstn?: boolean;
|
|
54
|
+
outboundPstn?: boolean;
|
|
55
|
+
inboundSip?: boolean;
|
|
56
|
+
outboundSip?: boolean;
|
|
57
|
+
sipRefer?: boolean;
|
|
58
|
+
bridgeTransfer?: boolean;
|
|
59
|
+
blindTransfer?: boolean;
|
|
60
|
+
attendedTransfer?: boolean;
|
|
61
|
+
warmTransfer?: boolean;
|
|
62
|
+
hold?: boolean;
|
|
63
|
+
resume?: boolean;
|
|
64
|
+
conference?: boolean;
|
|
65
|
+
dtmf?: boolean;
|
|
66
|
+
recording?: boolean;
|
|
67
|
+
statusCallbacks?: boolean;
|
|
68
|
+
regions?: TelephonyRegion[];
|
|
69
|
+
transports?: TelephonyTransport[];
|
|
70
|
+
mediaEncryption?: TelephonyMediaEncryption[];
|
|
71
|
+
codecs?: string[];
|
|
72
|
+
}
|
|
73
|
+
|
|
74
|
+
export interface TelephonyConnection {
|
|
75
|
+
id: string;
|
|
76
|
+
provider: TelephonyProvider;
|
|
77
|
+
name: string;
|
|
78
|
+
mode: TelephonyConnectionMode;
|
|
79
|
+
status?: "draft" | "active" | "disabled" | "error";
|
|
80
|
+
inboundUri?: string;
|
|
81
|
+
outboundProxy?: string;
|
|
82
|
+
authMode?: "ip_acl" | "credentials" | "none";
|
|
83
|
+
username?: string;
|
|
84
|
+
secretRef?: string;
|
|
85
|
+
allowedIps?: string[];
|
|
86
|
+
transport?: TelephonyTransport;
|
|
87
|
+
mediaEncryption?: TelephonyMediaEncryption;
|
|
88
|
+
codecs?: string[];
|
|
89
|
+
region?: TelephonyRegion;
|
|
90
|
+
capabilities?: TelephonyProviderCapabilities;
|
|
91
|
+
metadata?: Record<string, unknown>;
|
|
92
|
+
}
|
|
93
|
+
|
|
94
|
+
export type TelephonyDestination =
|
|
95
|
+
| {
|
|
96
|
+
type: "phone_number";
|
|
97
|
+
phoneNumber: string;
|
|
98
|
+
connectionId?: string;
|
|
99
|
+
callerId?: string;
|
|
100
|
+
metadata?: Record<string, unknown>;
|
|
101
|
+
}
|
|
102
|
+
| {
|
|
103
|
+
type: "sip_uri";
|
|
104
|
+
uri: string;
|
|
105
|
+
connectionId?: string;
|
|
106
|
+
transport?: TelephonyTransport;
|
|
107
|
+
metadata?: Record<string, unknown>;
|
|
108
|
+
}
|
|
109
|
+
| {
|
|
110
|
+
type: "extension";
|
|
111
|
+
extension: string;
|
|
112
|
+
connectionId: string;
|
|
113
|
+
metadata?: Record<string, unknown>;
|
|
114
|
+
}
|
|
115
|
+
| {
|
|
116
|
+
type: "queue";
|
|
117
|
+
queueId: string;
|
|
118
|
+
metadata?: Record<string, unknown>;
|
|
119
|
+
}
|
|
120
|
+
| {
|
|
121
|
+
type: "browser_queue";
|
|
122
|
+
queueId?: string;
|
|
123
|
+
label?: string;
|
|
124
|
+
metadata?: Record<string, unknown>;
|
|
125
|
+
};
|
|
126
|
+
|
|
127
|
+
export type TelephonyTransferMode = "blind" | "attended" | "warm";
|
|
128
|
+
|
|
129
|
+
export type TelephonyTransferFallback =
|
|
130
|
+
| "return_to_ai"
|
|
131
|
+
| "alternate_destination"
|
|
132
|
+
| "voicemail"
|
|
133
|
+
| "hangup";
|
|
134
|
+
|
|
135
|
+
export interface TelephonyTransferPolicy {
|
|
136
|
+
mode?: TelephonyTransferMode;
|
|
137
|
+
timeoutSeconds?: number;
|
|
138
|
+
fallback?: TelephonyTransferFallback;
|
|
139
|
+
fallbackDestination?: TelephonyDestination;
|
|
140
|
+
announceBeforeTransfer?: boolean;
|
|
141
|
+
allowSipRefer?: boolean;
|
|
142
|
+
}
|
|
143
|
+
|
|
144
|
+
export type TelephonyCallControlAction =
|
|
145
|
+
| "answer"
|
|
146
|
+
| "reject"
|
|
147
|
+
| "originate"
|
|
148
|
+
| "bridge"
|
|
149
|
+
| "transfer"
|
|
150
|
+
| "hold"
|
|
151
|
+
| "resume"
|
|
152
|
+
| "conference"
|
|
153
|
+
| "send_dtmf"
|
|
154
|
+
| "hangup";
|
|
155
|
+
|
|
156
|
+
export interface TelephonyPhoneNumber {
|
|
157
|
+
id?: string;
|
|
158
|
+
provider: TelephonyProvider;
|
|
159
|
+
phoneNumber: string;
|
|
160
|
+
label?: string | null;
|
|
161
|
+
providerNumberId?: string | null;
|
|
162
|
+
capabilities?: TelephonyNumberCapabilities;
|
|
163
|
+
inboundEnabled?: boolean;
|
|
164
|
+
outboundEnabled?: boolean;
|
|
165
|
+
metadata?: Record<string, unknown>;
|
|
166
|
+
}
|
|
167
|
+
|
|
168
|
+
export interface TelephonyInboundCallInput {
|
|
169
|
+
provider: TelephonyProvider;
|
|
170
|
+
providerCallId: string;
|
|
171
|
+
from: string;
|
|
172
|
+
to: string;
|
|
173
|
+
accountId?: string;
|
|
174
|
+
direction?: "inbound" | "outbound" | "unknown";
|
|
175
|
+
raw?: Record<string, unknown>;
|
|
176
|
+
}
|
|
177
|
+
|
|
178
|
+
export interface TelephonyOutboundCallInput {
|
|
179
|
+
from: string;
|
|
180
|
+
to: string;
|
|
181
|
+
answerUrl: string;
|
|
182
|
+
statusCallbackUrl?: string;
|
|
183
|
+
providerCallId?: string;
|
|
184
|
+
metadata?: Record<string, unknown>;
|
|
185
|
+
}
|
|
186
|
+
|
|
187
|
+
export interface TelephonyOutboundCallResult {
|
|
188
|
+
provider: TelephonyProvider;
|
|
189
|
+
providerCallId: string;
|
|
190
|
+
status: TelephonyCallStatus;
|
|
191
|
+
from: string;
|
|
192
|
+
to: string;
|
|
193
|
+
raw?: unknown;
|
|
194
|
+
}
|
|
195
|
+
|
|
196
|
+
export interface TelephonyCallControlResult {
|
|
197
|
+
provider: TelephonyProvider;
|
|
198
|
+
providerCallId: string;
|
|
199
|
+
status: TelephonyCallStatus;
|
|
200
|
+
raw?: unknown;
|
|
201
|
+
}
|
|
202
|
+
|
|
203
|
+
export interface TelephonyBaseCallControlInput {
|
|
204
|
+
providerCallId: string;
|
|
205
|
+
connectionId?: string;
|
|
206
|
+
metadata?: Record<string, unknown>;
|
|
207
|
+
}
|
|
208
|
+
|
|
209
|
+
export interface TelephonyAnswerCallInput extends TelephonyBaseCallControlInput {}
|
|
210
|
+
|
|
211
|
+
export interface TelephonyRejectCallInput extends TelephonyBaseCallControlInput {
|
|
212
|
+
reason?: string;
|
|
213
|
+
}
|
|
214
|
+
|
|
215
|
+
export interface TelephonyOriginateCallInput {
|
|
216
|
+
from?: TelephonyDestination;
|
|
217
|
+
to: TelephonyDestination;
|
|
218
|
+
connectionId?: string;
|
|
219
|
+
answerUrl?: string;
|
|
220
|
+
statusCallbackUrl?: string;
|
|
221
|
+
metadata?: Record<string, unknown>;
|
|
222
|
+
}
|
|
223
|
+
|
|
224
|
+
export interface TelephonyOriginateCallResult {
|
|
225
|
+
provider: TelephonyProvider;
|
|
226
|
+
providerCallId: string;
|
|
227
|
+
status: TelephonyCallStatus;
|
|
228
|
+
from?: TelephonyDestination;
|
|
229
|
+
to: TelephonyDestination;
|
|
230
|
+
raw?: unknown;
|
|
231
|
+
}
|
|
232
|
+
|
|
233
|
+
export interface TelephonyBridgeCallInput extends TelephonyBaseCallControlInput {
|
|
234
|
+
target: TelephonyDestination | { providerCallId: string };
|
|
235
|
+
policy?: TelephonyTransferPolicy;
|
|
236
|
+
}
|
|
237
|
+
|
|
238
|
+
export interface TelephonyTransferCallInput extends TelephonyBaseCallControlInput {
|
|
239
|
+
destination: TelephonyDestination;
|
|
240
|
+
mode?: TelephonyTransferMode;
|
|
241
|
+
reason?: string;
|
|
242
|
+
policy?: TelephonyTransferPolicy;
|
|
243
|
+
}
|
|
244
|
+
|
|
245
|
+
export interface TelephonyTransferCallResult extends TelephonyCallControlResult {
|
|
246
|
+
destination: TelephonyDestination;
|
|
247
|
+
mode: TelephonyTransferMode;
|
|
248
|
+
transferId?: string;
|
|
249
|
+
}
|
|
250
|
+
|
|
251
|
+
export interface TelephonyHoldCallInput extends TelephonyBaseCallControlInput {
|
|
252
|
+
reason?: string;
|
|
253
|
+
}
|
|
254
|
+
|
|
255
|
+
export interface TelephonyResumeCallInput extends TelephonyBaseCallControlInput {
|
|
256
|
+
reason?: string;
|
|
257
|
+
}
|
|
258
|
+
|
|
259
|
+
export interface TelephonyHangUpCallInput extends TelephonyBaseCallControlInput {
|
|
260
|
+
reason?: string;
|
|
261
|
+
}
|
|
262
|
+
|
|
263
|
+
export interface TelephonySendDtmfInput extends TelephonyBaseCallControlInput {
|
|
264
|
+
digits: string;
|
|
265
|
+
}
|
|
266
|
+
|
|
267
|
+
export type TelephonyCallEvent =
|
|
268
|
+
| {
|
|
269
|
+
type: "call.created" | "call.ringing" | "call.answered" | "call.ended";
|
|
270
|
+
provider: TelephonyProvider;
|
|
271
|
+
providerCallId: string;
|
|
272
|
+
connectionId?: string;
|
|
273
|
+
at?: string;
|
|
274
|
+
metadata?: Record<string, unknown>;
|
|
275
|
+
}
|
|
276
|
+
| {
|
|
277
|
+
type: "call.transfer.started" | "call.transfer.completed" | "call.transfer.failed";
|
|
278
|
+
provider: TelephonyProvider;
|
|
279
|
+
providerCallId: string;
|
|
280
|
+
transferId?: string;
|
|
281
|
+
destination?: TelephonyDestination;
|
|
282
|
+
reason?: string;
|
|
283
|
+
at?: string;
|
|
284
|
+
metadata?: Record<string, unknown>;
|
|
285
|
+
};
|
|
286
|
+
|
|
287
|
+
export interface ResolvedTelephonyConnection {
|
|
288
|
+
connection: TelephonyConnection;
|
|
289
|
+
provider: TelephonyProvider;
|
|
290
|
+
capabilities: TelephonyProviderCapabilities;
|
|
291
|
+
metadata?: Record<string, unknown>;
|
|
292
|
+
}
|
|
293
|
+
|
|
294
|
+
export interface TelephonyConnectionResolver {
|
|
295
|
+
resolveConnection(input: {
|
|
296
|
+
projectId: string;
|
|
297
|
+
accountId?: string;
|
|
298
|
+
agentId?: string;
|
|
299
|
+
destination?: TelephonyDestination;
|
|
300
|
+
channel?: "phone" | "sip" | "outbound";
|
|
301
|
+
metadata?: Record<string, unknown>;
|
|
302
|
+
}): Promise<ResolvedTelephonyConnection>;
|
|
303
|
+
}
|
|
304
|
+
|
|
305
|
+
export interface TelephonyProviderAdapter {
|
|
306
|
+
provider: TelephonyProvider;
|
|
307
|
+
createOutboundCall(input: TelephonyOutboundCallInput): Promise<TelephonyOutboundCallResult>;
|
|
308
|
+
}
|
|
309
|
+
|
|
310
|
+
export interface TelephonyCallControlAdapter {
|
|
311
|
+
provider: TelephonyProvider;
|
|
312
|
+
capabilities?(): TelephonyProviderCapabilities;
|
|
313
|
+
answerCall?(input: TelephonyAnswerCallInput): Promise<TelephonyCallControlResult>;
|
|
314
|
+
rejectCall?(input: TelephonyRejectCallInput): Promise<TelephonyCallControlResult>;
|
|
315
|
+
originateCall?(input: TelephonyOriginateCallInput): Promise<TelephonyOriginateCallResult>;
|
|
316
|
+
bridgeCall?(input: TelephonyBridgeCallInput): Promise<TelephonyCallControlResult>;
|
|
317
|
+
transferCall?(input: TelephonyTransferCallInput): Promise<TelephonyTransferCallResult>;
|
|
318
|
+
holdCall?(input: TelephonyHoldCallInput): Promise<TelephonyCallControlResult>;
|
|
319
|
+
resumeCall?(input: TelephonyResumeCallInput): Promise<TelephonyCallControlResult>;
|
|
320
|
+
sendDtmf?(input: TelephonySendDtmfInput): Promise<TelephonyCallControlResult>;
|
|
321
|
+
hangUpCall(input: {
|
|
322
|
+
providerCallId: string;
|
|
323
|
+
reason?: string;
|
|
324
|
+
metadata?: Record<string, unknown>;
|
|
325
|
+
}): Promise<TelephonyCallControlResult>;
|
|
326
|
+
}
|