@memberjunction/ai-bridge-twilio 0.0.1 → 5.42.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 +106 -43
- package/dist/index.d.ts +5 -0
- package/dist/index.d.ts.map +1 -0
- package/dist/index.js +12 -0
- package/dist/index.js.map +1 -0
- package/dist/register-native.d.ts +19 -0
- package/dist/register-native.d.ts.map +1 -0
- package/dist/register-native.js +34 -0
- package/dist/register-native.js.map +1 -0
- package/dist/twilio-bridge.d.ts +54 -0
- package/dist/twilio-bridge.d.ts.map +1 -0
- package/dist/twilio-bridge.js +75 -0
- package/dist/twilio-bridge.js.map +1 -0
- package/dist/twilio-call-sdk.d.ts +167 -0
- package/dist/twilio-call-sdk.d.ts.map +1 -0
- package/dist/twilio-call-sdk.js +156 -0
- package/dist/twilio-call-sdk.js.map +1 -0
- package/dist/twilio-native-call-sdk.d.ts +281 -0
- package/dist/twilio-native-call-sdk.d.ts.map +1 -0
- package/dist/twilio-native-call-sdk.js +313 -0
- package/dist/twilio-native-call-sdk.js.map +1 -0
- package/package.json +32 -7
|
@@ -0,0 +1,156 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* @fileoverview The **Twilio** binding of the telephony call SDK seam — `TwilioCallSdk` +
|
|
3
|
+
* `TwilioCallSdkFactory`.
|
|
4
|
+
*
|
|
5
|
+
* {@link TwilioCallSdk} implements the platform-agnostic {@link ITelephonyCallSdk} (from
|
|
6
|
+
* `@memberjunction/ai-bridge-base`) over **Twilio Programmable Voice + Media Streams**, so
|
|
7
|
+
* {@link import('./twilio-bridge.js').TwilioBridge} drives a real phone call without ever importing the
|
|
8
|
+
* Twilio SDK directly. As shipped, every operation throws an explicit **"bind the real Twilio client"**
|
|
9
|
+
* error — the real Twilio binding is a documented deployment seam (below). Production supplies the bound
|
|
10
|
+
* client via {@link TwilioCallSdk}'s constructor / the factory; tests inject a `FakeTwilioCallSdk`.
|
|
11
|
+
*
|
|
12
|
+
* ## How the Twilio operations map (production binding — deployment TODO)
|
|
13
|
+
* Twilio has two halves: a **REST API** (place/modify/hang-up calls) and a **Media Streams** websocket
|
|
14
|
+
* (bidirectional realtime audio + DTMF events). This SDK adapter sits over both:
|
|
15
|
+
*
|
|
16
|
+
* | {@link ITelephonyCallSdk} op | Twilio binding |
|
|
17
|
+
* |---|---|
|
|
18
|
+
* | {@link TwilioCallSdk.dial} (outbound) | REST `client.calls.create({ to, from, twiml })` where the TwiML opens a bidirectional `<Connect><Stream>` to the Media-Streams websocket. Returns the Call SID. |
|
|
19
|
+
* | {@link TwilioCallSdk.answer} (inbound) | The inbound webhook (a call to the agent's DID) returns TwiML with `<Connect><Stream>`; "answering" = accepting that Media-Streams websocket for the delivered Call SID. |
|
|
20
|
+
* | {@link TwilioCallSdk.hangup} | REST `client.calls(callSid).update({ status: 'completed' })`. |
|
|
21
|
+
* | {@link TwilioCallSdk.sendAudioFrame} | A Media-Streams **outbound `media`** message (base64 μ-law/PCM payload) on the call's websocket — the agent's voice. |
|
|
22
|
+
* | {@link TwilioCallSdk.onAudioFrame} | Inbound Media-Streams **`media`** events on the websocket — what the agent hears (single remote party). |
|
|
23
|
+
* | {@link TwilioCallSdk.sendDtmf} | REST `client.calls(callSid).update({ twiml: '<Play digits="...">' })` (or a `<Dial>` `sendDigits`). |
|
|
24
|
+
* | {@link TwilioCallSdk.onDtmf} | `<Gather input="dtmf">` results delivered to the voice webhook, OR Media-Streams DTMF `dtmf` events. |
|
|
25
|
+
* | {@link TwilioCallSdk.transfer} | REST `client.calls(callSid).update({ twiml: '<Dial>+1...</Dial>' })` redirecting the live call. |
|
|
26
|
+
* | {@link TwilioCallSdk.onCallEnded} | The `status-callback` webhook for `completed` / `failed` / `canceled`, OR the Media-Streams `stop` event. |
|
|
27
|
+
*
|
|
28
|
+
* Binding the real Twilio client is a thin adapter that supplies an {@link ITwilioClientBindings}
|
|
29
|
+
* implementation (REST + websocket) to {@link TwilioCallSdk}; the driver and its tests do not change.
|
|
30
|
+
* **None of the `twilio` SDK's types leak into this package.**
|
|
31
|
+
*
|
|
32
|
+
* Credentials (Account SID, Auth Token, the Media-Streams websocket URL) resolve through MJ's credential
|
|
33
|
+
* system referenced by the provider `Configuration` — never inline secrets.
|
|
34
|
+
*
|
|
35
|
+
* @module @memberjunction/ai-bridge-twilio
|
|
36
|
+
* @see {@link ITelephonyCallSdk} — the platform-agnostic seam this binds.
|
|
37
|
+
* @see `/plans/realtime/realtime-bridges-architecture.md` §8 (Twilio capability row) and §9 Phase 6.
|
|
38
|
+
*/
|
|
39
|
+
/** The default bindings used when none are supplied — every operation throws the bind-me error. */
|
|
40
|
+
const UNBOUND_BINDINGS = {
|
|
41
|
+
createCall: () => throwUnbound('createCall (outbound dial)'),
|
|
42
|
+
acceptInbound: () => throwUnbound('acceptInbound (inbound answer)'),
|
|
43
|
+
completeCall: () => throwUnbound('completeCall (hangup)'),
|
|
44
|
+
pushStreamAudio: () => throwUnboundVoid('pushStreamAudio (outbound media)'),
|
|
45
|
+
onStreamAudio: () => throwUnboundVoid('onStreamAudio (inbound media)'),
|
|
46
|
+
playDigits: () => throwUnbound('playDigits (send DTMF)'),
|
|
47
|
+
onDigits: () => throwUnboundVoid('onDigits (receive DTMF)'),
|
|
48
|
+
redirectCall: () => throwUnbound('redirectCall (transfer)'),
|
|
49
|
+
onCallStatus: () => throwUnboundVoid('onCallStatus (call ended)'),
|
|
50
|
+
};
|
|
51
|
+
function throwUnbound(op) {
|
|
52
|
+
throw new Error(`TwilioCallSdk has no real Twilio client bound (operation '${op}'). Construct it with an ` +
|
|
53
|
+
'ITwilioClientBindings over the real twilio SDK (REST + Media Streams), or inject a fake in tests.');
|
|
54
|
+
}
|
|
55
|
+
function throwUnboundVoid(op) {
|
|
56
|
+
throwUnbound(op);
|
|
57
|
+
}
|
|
58
|
+
/**
|
|
59
|
+
* Twilio's implementation of the {@link ITelephonyCallSdk} telephony seam over **Twilio Programmable
|
|
60
|
+
* Voice + Media Streams**. {@link import('./twilio-bridge.js').TwilioBridge} drives this; production
|
|
61
|
+
* constructs it with real {@link ITwilioClientBindings}, tests inject a `FakeTwilioCallSdk` directly
|
|
62
|
+
* (no need for this adapter at all in tests).
|
|
63
|
+
*
|
|
64
|
+
* The adapter tracks the active Call SID so the per-call websocket / REST operations can be addressed.
|
|
65
|
+
* It never imports the `twilio` SDK — all Twilio specifics live behind {@link ITwilioClientBindings}.
|
|
66
|
+
*/
|
|
67
|
+
export class TwilioCallSdk {
|
|
68
|
+
/**
|
|
69
|
+
* @param bindings The real Twilio client bindings (REST + Media Streams). Defaults to an unbound
|
|
70
|
+
* implementation whose every method throws the explicit "bind the real Twilio client" error.
|
|
71
|
+
*/
|
|
72
|
+
constructor(bindings = UNBOUND_BINDINGS) {
|
|
73
|
+
this.bindings = bindings;
|
|
74
|
+
/** The active call's SID once dialled/answered, so per-call operations can address it. */
|
|
75
|
+
this.activeCallSid = null;
|
|
76
|
+
}
|
|
77
|
+
/** @inheritdoc */
|
|
78
|
+
async dial(toNumber, fromNumber, args) {
|
|
79
|
+
const sid = await this.bindings.createCall(toNumber, fromNumber, args);
|
|
80
|
+
this.bindCall(sid);
|
|
81
|
+
return sid;
|
|
82
|
+
}
|
|
83
|
+
/** @inheritdoc */
|
|
84
|
+
async answer(callId) {
|
|
85
|
+
await this.bindings.acceptInbound(callId);
|
|
86
|
+
this.bindCall(callId);
|
|
87
|
+
}
|
|
88
|
+
/** @inheritdoc */
|
|
89
|
+
async hangup(callId) {
|
|
90
|
+
await this.bindings.completeCall(callId);
|
|
91
|
+
this.activeCallSid = null;
|
|
92
|
+
}
|
|
93
|
+
/** @inheritdoc */
|
|
94
|
+
sendAudioFrame(pcm) {
|
|
95
|
+
if (this.activeCallSid) {
|
|
96
|
+
this.bindings.pushStreamAudio(this.activeCallSid, pcm);
|
|
97
|
+
}
|
|
98
|
+
}
|
|
99
|
+
/** @inheritdoc */
|
|
100
|
+
onAudioFrame(cb) {
|
|
101
|
+
this.audioCb = cb;
|
|
102
|
+
if (this.activeCallSid) {
|
|
103
|
+
this.bindings.onStreamAudio(this.activeCallSid, cb);
|
|
104
|
+
}
|
|
105
|
+
}
|
|
106
|
+
/** @inheritdoc */
|
|
107
|
+
async sendDtmf(digits) {
|
|
108
|
+
if (this.activeCallSid) {
|
|
109
|
+
await this.bindings.playDigits(this.activeCallSid, digits);
|
|
110
|
+
}
|
|
111
|
+
}
|
|
112
|
+
/** @inheritdoc */
|
|
113
|
+
onDtmf(cb) {
|
|
114
|
+
this.dtmfCb = cb;
|
|
115
|
+
if (this.activeCallSid) {
|
|
116
|
+
this.bindings.onDigits(this.activeCallSid, cb);
|
|
117
|
+
}
|
|
118
|
+
}
|
|
119
|
+
/** @inheritdoc */
|
|
120
|
+
async transfer(callId, toNumber) {
|
|
121
|
+
await this.bindings.redirectCall(callId, toNumber);
|
|
122
|
+
}
|
|
123
|
+
/** @inheritdoc */
|
|
124
|
+
onCallEnded(cb) {
|
|
125
|
+
this.endedCb = cb;
|
|
126
|
+
if (this.activeCallSid) {
|
|
127
|
+
this.bindings.onCallStatus(this.activeCallSid, cb);
|
|
128
|
+
}
|
|
129
|
+
}
|
|
130
|
+
/** Binds the active Call SID and (re-)registers any handlers the driver set before the call existed. */
|
|
131
|
+
bindCall(callSid) {
|
|
132
|
+
this.activeCallSid = callSid;
|
|
133
|
+
if (this.audioCb) {
|
|
134
|
+
this.bindings.onStreamAudio(callSid, this.audioCb);
|
|
135
|
+
}
|
|
136
|
+
if (this.dtmfCb) {
|
|
137
|
+
this.bindings.onDigits(callSid, this.dtmfCb);
|
|
138
|
+
}
|
|
139
|
+
if (this.endedCb) {
|
|
140
|
+
this.bindings.onCallStatus(callSid, this.endedCb);
|
|
141
|
+
}
|
|
142
|
+
}
|
|
143
|
+
}
|
|
144
|
+
/**
|
|
145
|
+
* Builds a {@link TwilioCallSdk} for a session — the Twilio creation seam. Production supplies a factory
|
|
146
|
+
* that constructs the SDK with real {@link ITwilioClientBindings} (built from the resolved provider
|
|
147
|
+
* `Configuration` — Account SID, Auth Token, Media-Streams URL, all resolved upstream). Out of the box
|
|
148
|
+
* this returns an **unbound** `TwilioCallSdk` whose operations throw until the real client is bound.
|
|
149
|
+
*
|
|
150
|
+
* @param _config The resolved provider/session configuration (credential refs already resolved upstream).
|
|
151
|
+
* @returns An (unbound) {@link TwilioCallSdk}. Override in deployment / inject a fake in tests.
|
|
152
|
+
*/
|
|
153
|
+
export const TwilioCallSdkFactory = (_config) => {
|
|
154
|
+
return new TwilioCallSdk();
|
|
155
|
+
};
|
|
156
|
+
//# sourceMappingURL=twilio-call-sdk.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"twilio-call-sdk.js","sourceRoot":"","sources":["../src/twilio-call-sdk.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,281 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* @fileoverview `TwilioNativeCallSdk` — a **real, two-way** {@link ITelephonyCallSdk} binding over the
|
|
3
|
+
* native **Twilio Programmable Voice + Media Streams** path. Unlike the shipped, unbound
|
|
4
|
+
* {@link import('./twilio-call-sdk.js').TwilioCallSdk} (every operation throws "bind the real Twilio
|
|
5
|
+
* client"), this adapter actually carries the call: {@link TwilioNativeCallSdk.sendAudioFrame} forwards
|
|
6
|
+
* the agent's synthesized PCM onto the call's Media-Streams websocket (the agent's **voice** on the
|
|
7
|
+
* call), inbound Media-Streams audio maps to the seam's audio-frame callback (what the agent **hears**),
|
|
8
|
+
* and DTMF / transfer / hangup actually reach the native client.
|
|
9
|
+
*
|
|
10
|
+
* ## Why a native binding
|
|
11
|
+
* A phone call is a single-leg, bidirectional audio source. The native Twilio path has two halves — a
|
|
12
|
+
* **REST API** (place / hang-up / transfer / send-DTMF) and a **Media Streams** websocket (bidirectional
|
|
13
|
+
* realtime audio + inbound DTMF events). This adapter sits over both so a single binding gives the agent
|
|
14
|
+
* full-duplex audio plus the call controls the telephony seam declares.
|
|
15
|
+
*
|
|
16
|
+
* ## The native module seam (no SDK types leak)
|
|
17
|
+
* The `twilio` SDK is NOT imported here. Deployments wrap the REST + Media-Streams plumbing behind the
|
|
18
|
+
* small structural {@link NativeCallModule} surface declared below, and this adapter depends ONLY on that
|
|
19
|
+
* surface — none of `twilio`'s real types leak into `@memberjunction/ai-bridge-twilio`. The module is
|
|
20
|
+
* loaded **lazily** behind an injectable {@link NativeModuleLoader} so the package builds and unit-tests
|
|
21
|
+
* with **no `twilio` install and no network**. Tests inject a fake module; production points
|
|
22
|
+
* {@link TwilioNativeSdkConfig.NativeModuleSpecifier} at the real adapter package/path.
|
|
23
|
+
*
|
|
24
|
+
* ## Auth (deployment responsibility)
|
|
25
|
+
* The native client authenticates with the Twilio **Account SID + Auth Token** (or API key/secret) and a
|
|
26
|
+
* resolved Media-Streams websocket URL. These resolve **upstream** (MJ credential system / provider
|
|
27
|
+
* `Configuration`) and are **never inlined** at a call site — {@link readNativeConfig} reads already-
|
|
28
|
+
* resolved values out of the loose `Configuration` map the engine passes at connect.
|
|
29
|
+
*
|
|
30
|
+
* @module @memberjunction/ai-bridge-twilio
|
|
31
|
+
* @author MemberJunction.com
|
|
32
|
+
* @see {@link ITelephonyCallSdk} — the platform-agnostic telephony seam this binds.
|
|
33
|
+
*/
|
|
34
|
+
import { ITelephonyCallSdk } from '@memberjunction/ai-bridge-base';
|
|
35
|
+
/** One inbound Media-Streams audio frame the native adapter surfaces (what the agent hears). */
|
|
36
|
+
export interface NativeCallAudioFrame {
|
|
37
|
+
/** Raw PCM/μ-law bytes for this frame (`Uint8Array` view or standalone `ArrayBuffer`). */
|
|
38
|
+
data: Uint8Array | ArrayBuffer;
|
|
39
|
+
/** Optional epoch-ms capture timestamp (informational; the seam carries only the bytes). */
|
|
40
|
+
timestampMs?: number;
|
|
41
|
+
}
|
|
42
|
+
/** Options the native adapter's `createCall` (outbound dial) accepts. Mirrors the seam's `dial` args. */
|
|
43
|
+
export interface NativeDialArgs {
|
|
44
|
+
/** Destination number to dial (E.164 recommended). */
|
|
45
|
+
toNumber: string;
|
|
46
|
+
/** The agent's caller-id / DID the call originates FROM. */
|
|
47
|
+
fromNumber: string;
|
|
48
|
+
/** Provider-specific dial options (status-callback URL, stream URL, recording flags, …). */
|
|
49
|
+
extra?: Record<string, unknown>;
|
|
50
|
+
}
|
|
51
|
+
/**
|
|
52
|
+
* The live native-call client the adapter hands back. The surface mirrors {@link ITelephonyCallSdk} but in
|
|
53
|
+
* the native adapter's own (lower-cased) vocabulary; this adapter maps between the two. VERIFY against the
|
|
54
|
+
* native Twilio adapter.
|
|
55
|
+
*/
|
|
56
|
+
export interface NativeCallClient {
|
|
57
|
+
/** Places the outbound call (REST `calls.create` with a `<Connect><Stream>` TwiML). Resolves the Call SID. */
|
|
58
|
+
dial(args: NativeDialArgs): Promise<string>;
|
|
59
|
+
/** Accepts the Media-Streams websocket for an already-delivered inbound Call SID. */
|
|
60
|
+
answer(callId: string): Promise<void>;
|
|
61
|
+
/** Ends the call (REST `calls(sid).update({ status: 'completed' })`). */
|
|
62
|
+
hangup(callId: string): Promise<void>;
|
|
63
|
+
/** Pushes one outbound PCM frame onto the call's Media-Streams websocket — the agent's voice. */
|
|
64
|
+
sendAudioFrame(pcm: ArrayBuffer): void;
|
|
65
|
+
/** Registers the inbound Media-Streams audio callback. "Latest handler wins." */
|
|
66
|
+
onAudioFrame(cb: (frame: NativeCallAudioFrame) => void): void;
|
|
67
|
+
/** Sends DTMF digits on the call (REST `<Play digits>` / `<Dial sendDigits>`). */
|
|
68
|
+
sendDtmf(digits: string): Promise<void>;
|
|
69
|
+
/** Registers the inbound DTMF callback (`<Gather>` results or Media-Streams `dtmf` events). */
|
|
70
|
+
onDtmf(cb: (digits: string) => void): void;
|
|
71
|
+
/** Transfers the live call (REST `calls(sid).update({ twiml: '<Dial>...' })`). */
|
|
72
|
+
transfer(callId: string, toNumber: string): Promise<void>;
|
|
73
|
+
/** Registers the call-ended callback (status-callback `completed`/`failed` or stream `stop`). */
|
|
74
|
+
onCallEnded(cb: () => void): void;
|
|
75
|
+
}
|
|
76
|
+
/** Options passed to {@link NativeCallModule.createClient}. Credentials are resolved upstream. */
|
|
77
|
+
export interface NativeClientOptions {
|
|
78
|
+
/** The Twilio Account SID (resolved upstream). */
|
|
79
|
+
AccountSid?: string;
|
|
80
|
+
/** The Twilio Auth Token (resolved upstream). */
|
|
81
|
+
AuthToken?: string;
|
|
82
|
+
/** The Twilio API Key SID (resolved upstream; alternative to Account SID + Auth Token). */
|
|
83
|
+
ApiKeySid?: string;
|
|
84
|
+
/** The Twilio API Key Secret (resolved upstream). */
|
|
85
|
+
ApiKeySecret?: string;
|
|
86
|
+
/** The Media-Streams websocket URL the call's `<Connect><Stream>` connects to (resolved upstream). */
|
|
87
|
+
StreamUrl?: string;
|
|
88
|
+
}
|
|
89
|
+
/** The native adapter module surface — a factory that constructs a {@link NativeCallClient}. */
|
|
90
|
+
export interface NativeCallModule {
|
|
91
|
+
/**
|
|
92
|
+
* Constructs a native call client from resolved options (Twilio credentials already resolved upstream).
|
|
93
|
+
* VERIFY against the native Twilio adapter (`createClient` vs a `Client` constructor).
|
|
94
|
+
*/
|
|
95
|
+
createClient(options: NativeClientOptions): NativeCallClient;
|
|
96
|
+
}
|
|
97
|
+
/**
|
|
98
|
+
* Resolved configuration for {@link TwilioNativeCallSdk}. Credentials resolve **upstream** (MJ credential
|
|
99
|
+
* system / provider `Configuration`) — this object carries already-resolved values; **never inline
|
|
100
|
+
* secrets** at a call site.
|
|
101
|
+
*/
|
|
102
|
+
export interface TwilioNativeSdkConfig {
|
|
103
|
+
/** The Twilio Account SID (resolved upstream). */
|
|
104
|
+
AccountSid?: string;
|
|
105
|
+
/** The Twilio Auth Token (resolved upstream). */
|
|
106
|
+
AuthToken?: string;
|
|
107
|
+
/** The Twilio API Key SID (resolved upstream; alternative to Account SID + Auth Token). */
|
|
108
|
+
ApiKeySid?: string;
|
|
109
|
+
/** The Twilio API Key Secret (resolved upstream). */
|
|
110
|
+
ApiKeySecret?: string;
|
|
111
|
+
/** The Media-Streams websocket URL the call's `<Connect><Stream>` connects to (resolved upstream). */
|
|
112
|
+
StreamUrl?: string;
|
|
113
|
+
/**
|
|
114
|
+
* The module specifier of the native Twilio adapter to load (an internal adapter package name or an
|
|
115
|
+
* absolute path to a sidecar entry). Required in production; tests inject a loader.
|
|
116
|
+
*/
|
|
117
|
+
NativeModuleSpecifier?: string;
|
|
118
|
+
}
|
|
119
|
+
/**
|
|
120
|
+
* Copies arbitrary PCM bytes into a standalone `ArrayBuffer`, never aliasing the caller's buffer window.
|
|
121
|
+
* A `Uint8Array` view may be a slice of a larger backing buffer (offset/length), so a naive `.buffer`
|
|
122
|
+
* grab would leak neighbouring audio; this copies exactly the view's bytes. Defined locally because the
|
|
123
|
+
* Twilio package has no shared coercion helper to reuse (unlike the Zoom package's RTMS binding).
|
|
124
|
+
*/
|
|
125
|
+
export declare function toArrayBuffer(data: Uint8Array | ArrayBuffer): ArrayBuffer;
|
|
126
|
+
/**
|
|
127
|
+
* **Pure mapping** of one native inbound audio frame onto the raw PCM `ArrayBuffer` the telephony seam's
|
|
128
|
+
* audio callback delivers. Copies the bytes (see {@link toArrayBuffer}). Isolated from I/O for direct
|
|
129
|
+
* unit testing. (The telephony seam carries no per-speaker label — a 1:1 call has one remote party.)
|
|
130
|
+
*/
|
|
131
|
+
export declare function mapNativeAudioFrame(frame: NativeCallAudioFrame): ArrayBuffer;
|
|
132
|
+
/**
|
|
133
|
+
* The injectable loader for the native Twilio adapter — overridable so unit tests supply a fake module
|
|
134
|
+
* with no `twilio` install and no network. Production leaves it as {@link defaultNativeLoader}, which
|
|
135
|
+
* lazily imports the configured {@link TwilioNativeSdkConfig.NativeModuleSpecifier}.
|
|
136
|
+
*/
|
|
137
|
+
export type NativeModuleLoader = (specifier: string) => Promise<NativeCallModule>;
|
|
138
|
+
/**
|
|
139
|
+
* Lazily loads the native Twilio adapter at the given specifier (category: runtime plugin discovery from
|
|
140
|
+
* config — the adapter path is deployment-supplied and not known at build time, so a static import is
|
|
141
|
+
* impossible). Throws a precise, actionable error when it can't be loaded so a misconfigured deployment
|
|
142
|
+
* fails loudly, not silently.
|
|
143
|
+
*
|
|
144
|
+
* VERIFY against the native Twilio adapter: the module's default/namespace interop + that it exposes
|
|
145
|
+
* `createClient`.
|
|
146
|
+
*/
|
|
147
|
+
export declare const defaultNativeLoader: NativeModuleLoader;
|
|
148
|
+
/**
|
|
149
|
+
* A **real, two-way** {@link ITelephonyCallSdk} over the native Twilio Programmable Voice + Media Streams
|
|
150
|
+
* path.
|
|
151
|
+
*
|
|
152
|
+
* Gives the agent both **hearing** (inbound Media-Streams audio → the seam's audio callback) and a
|
|
153
|
+
* **voice** ({@link sendAudioFrame} → the native Media-Streams send path), plus working DTMF, transfer,
|
|
154
|
+
* and hangup. Construct via {@link BindTwilioNativeCall} (the factory the bridge's `SetSdkFactory` wants),
|
|
155
|
+
* not directly, so config resolution + the lazy loader wire consistently.
|
|
156
|
+
*
|
|
157
|
+
* The adapter tracks the active Call SID so per-call REST/websocket operations can be addressed, and
|
|
158
|
+
* re-registers any handlers the bridge set before the call existed once a call comes online.
|
|
159
|
+
*/
|
|
160
|
+
export declare class TwilioNativeCallSdk implements ITelephonyCallSdk {
|
|
161
|
+
/** Resolved config (credentials + stream URL + the native module specifier). */
|
|
162
|
+
private readonly config;
|
|
163
|
+
/** The native-adapter module loader (overridable for tests). */
|
|
164
|
+
private readonly loadModule;
|
|
165
|
+
/** The live native client once {@link dial}/{@link answer} brings a call online. */
|
|
166
|
+
private client;
|
|
167
|
+
/** The active call's SID, so per-call operations can address it. */
|
|
168
|
+
private activeCallId;
|
|
169
|
+
/** The inbound audio handler the bridge registered (may predate the call). */
|
|
170
|
+
private audioHandler?;
|
|
171
|
+
/** The inbound DTMF handler the bridge registered (may predate the call). */
|
|
172
|
+
private dtmfHandler?;
|
|
173
|
+
/** The call-ended handler the bridge registered (may predate the call). */
|
|
174
|
+
private endedHandler?;
|
|
175
|
+
/**
|
|
176
|
+
* @param config Resolved credentials + stream URL + the native module specifier.
|
|
177
|
+
* @param loadModule The native-adapter loader (defaults to the lazy specifier loader).
|
|
178
|
+
*/
|
|
179
|
+
constructor(config: TwilioNativeSdkConfig, loadModule?: NativeModuleLoader);
|
|
180
|
+
/**
|
|
181
|
+
* Loads the native adapter, constructs a client, wires its callbacks, and places the **outbound** call.
|
|
182
|
+
* Brings BOTH hearing and the agent's voice online. Resolves the platform-native Call SID.
|
|
183
|
+
*
|
|
184
|
+
* @param toNumber The destination phone number to dial.
|
|
185
|
+
* @param fromNumber The agent's caller-id / DID the call originates from.
|
|
186
|
+
* @param args Provider-specific dial parameters.
|
|
187
|
+
* @returns The Twilio Call SID for the placed call.
|
|
188
|
+
* @throws When the native module specifier is missing or the adapter can't be loaded.
|
|
189
|
+
*/
|
|
190
|
+
dial(toNumber: string, fromNumber: string, args?: Record<string, unknown>): Promise<string>;
|
|
191
|
+
/**
|
|
192
|
+
* Loads the native adapter, constructs a client, wires its callbacks, and **answers** an inbound call
|
|
193
|
+
* already delivered to the agent's number. Brings BOTH hearing and the agent's voice online.
|
|
194
|
+
*
|
|
195
|
+
* @param callId The platform-native identifier of the inbound call to answer.
|
|
196
|
+
* @throws When the native module specifier is missing or the adapter can't be loaded.
|
|
197
|
+
*/
|
|
198
|
+
answer(callId: string): Promise<void>;
|
|
199
|
+
/**
|
|
200
|
+
* Hangs up the call via the native client and clears the active call. Tolerant of teardown errors.
|
|
201
|
+
*
|
|
202
|
+
* @param callId The platform-native identifier of the call to end.
|
|
203
|
+
*/
|
|
204
|
+
hangup(callId: string): Promise<void>;
|
|
205
|
+
/**
|
|
206
|
+
* Sends one raw PCM frame as the agent's outbound voice into the call — **the agent's real voice on
|
|
207
|
+
* the call**, via the native Media-Streams send path. No-ops (without throwing) before a call exists
|
|
208
|
+
* so an early model frame never crashes the session.
|
|
209
|
+
*
|
|
210
|
+
* @param pcm The PCM audio bytes to speak into the call.
|
|
211
|
+
*/
|
|
212
|
+
sendAudioFrame(pcm: ArrayBuffer): void;
|
|
213
|
+
/**
|
|
214
|
+
* Registers the inbound audio handler (the agent's hearing path). Wires it to the live client when a
|
|
215
|
+
* call already exists; otherwise it is (re-)registered at {@link bindCall} when the call comes online.
|
|
216
|
+
*
|
|
217
|
+
* @param cb Invoked with each inbound PCM audio frame.
|
|
218
|
+
*/
|
|
219
|
+
onAudioFrame(cb: (pcm: ArrayBuffer) => void): void;
|
|
220
|
+
/**
|
|
221
|
+
* Sends DTMF touch-tones on the call via the native client. No-ops (without throwing) before a call
|
|
222
|
+
* exists.
|
|
223
|
+
*
|
|
224
|
+
* @param digits The DTMF digit string to send.
|
|
225
|
+
*/
|
|
226
|
+
sendDtmf(digits: string): Promise<void>;
|
|
227
|
+
/**
|
|
228
|
+
* Registers the inbound DTMF handler. Wires it to the live client when a call already exists; otherwise
|
|
229
|
+
* it is (re-)registered at {@link bindCall} when the call comes online.
|
|
230
|
+
*
|
|
231
|
+
* @param cb Invoked with each received DTMF digit string.
|
|
232
|
+
*/
|
|
233
|
+
onDtmf(cb: (digits: string) => void): void;
|
|
234
|
+
/**
|
|
235
|
+
* Transfers the live call to another party via the native client. No-ops (without throwing) before a
|
|
236
|
+
* call exists.
|
|
237
|
+
*
|
|
238
|
+
* @param callId The platform-native identifier of the call to transfer.
|
|
239
|
+
* @param toNumber The transfer destination.
|
|
240
|
+
*/
|
|
241
|
+
transfer(callId: string, toNumber: string): Promise<void>;
|
|
242
|
+
/**
|
|
243
|
+
* Registers the call-ended handler. Wires it to the live client when a call already exists; otherwise
|
|
244
|
+
* it is (re-)registered at {@link bindCall} when the call comes online.
|
|
245
|
+
*
|
|
246
|
+
* @param cb Invoked when the call ends.
|
|
247
|
+
*/
|
|
248
|
+
onCallEnded(cb: () => void): void;
|
|
249
|
+
/**
|
|
250
|
+
* Lazily loads the native adapter and constructs the client (once), throwing an actionable error when
|
|
251
|
+
* the required {@link TwilioNativeSdkConfig.NativeModuleSpecifier} is absent.
|
|
252
|
+
*/
|
|
253
|
+
private ensureClient;
|
|
254
|
+
/** Binds the active Call SID and (re-)registers any handlers the bridge set before the call existed. */
|
|
255
|
+
private bindCall;
|
|
256
|
+
}
|
|
257
|
+
/**
|
|
258
|
+
* Builds the {@link import('@memberjunction/ai-bridge-base').TelephonyCallSdkFactory}-shaped factory that
|
|
259
|
+
* constructs a {@link TwilioNativeCallSdk} from the bridge's per-session `Configuration`. Pass the result
|
|
260
|
+
* to `TwilioBridge.SetSdkFactory(...)` so a deployment activates **two-way native Twilio audio** without
|
|
261
|
+
* code changes to the driver.
|
|
262
|
+
*
|
|
263
|
+
* Reads Twilio credentials + stream URL + the native module specifier out of the config map the engine
|
|
264
|
+
* passes at connect (credentials resolved upstream — **never inline secrets**).
|
|
265
|
+
*
|
|
266
|
+
* @example
|
|
267
|
+
* // Where bridge drivers are configured (creds already resolved from the MJ credential system):
|
|
268
|
+
* bridge.SetSdkFactory(BindTwilioNativeCall());
|
|
269
|
+
*
|
|
270
|
+
* @param loadModule Optional native-adapter loader override (tests inject a fake; production omits it).
|
|
271
|
+
* @returns A factory `(config) => TwilioNativeCallSdk`.
|
|
272
|
+
*/
|
|
273
|
+
export declare function BindTwilioNativeCall(loadModule?: NativeModuleLoader): (config?: Record<string, unknown>) => TwilioNativeCallSdk;
|
|
274
|
+
/**
|
|
275
|
+
* Extracts a {@link TwilioNativeSdkConfig} from the engine's loosely-typed `Configuration` map without
|
|
276
|
+
* ever widening to `any`. Each field is read + type-checked individually so a malformed config yields a
|
|
277
|
+
* clean, partially-resolved object (and {@link TwilioNativeCallSdk} then throws a precise error if the
|
|
278
|
+
* required specifier is absent) rather than a half-typed blob.
|
|
279
|
+
*/
|
|
280
|
+
export declare function readNativeConfig(config?: Record<string, unknown>): TwilioNativeSdkConfig;
|
|
281
|
+
//# sourceMappingURL=twilio-native-call-sdk.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"twilio-native-call-sdk.d.ts","sourceRoot":"","sources":["../src/twilio-native-call-sdk.ts"],"names":[],"mappings":"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"}
|