@framers/agentos 0.1.110 → 0.1.112
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/api/agency.d.ts.map +1 -1
- package/dist/api/agency.js +38 -2
- package/dist/api/agency.js.map +1 -1
- package/dist/api/agent.js +1 -1
- package/dist/api/agent.js.map +1 -1
- package/dist/api/strategies/debate.d.ts.map +1 -1
- package/dist/api/strategies/debate.js.map +1 -1
- package/dist/api/strategies/graph.d.ts.map +1 -1
- package/dist/api/strategies/graph.js +1 -2
- package/dist/api/strategies/graph.js.map +1 -1
- package/dist/api/strategies/hierarchical.d.ts.map +1 -1
- package/dist/api/strategies/hierarchical.js +1 -2
- package/dist/api/strategies/hierarchical.js.map +1 -1
- package/dist/api/strategies/index.d.ts +1 -9
- package/dist/api/strategies/index.d.ts.map +1 -1
- package/dist/api/strategies/index.js +1 -11
- package/dist/api/strategies/index.js.map +1 -1
- package/dist/api/strategies/parallel.d.ts.map +1 -1
- package/dist/api/strategies/parallel.js +23 -4
- package/dist/api/strategies/parallel.js.map +1 -1
- package/dist/api/strategies/review-loop.d.ts.map +1 -1
- package/dist/api/strategies/review-loop.js.map +1 -1
- package/dist/api/strategies/sequential.d.ts.map +1 -1
- package/dist/api/strategies/sequential.js +1 -2
- package/dist/api/strategies/sequential.js.map +1 -1
- package/dist/api/strategies/shared.d.ts +8 -0
- package/dist/api/strategies/shared.d.ts.map +1 -1
- package/dist/api/strategies/shared.js +10 -1
- package/dist/api/strategies/shared.js.map +1 -1
- package/dist/api/types.d.ts +6 -0
- package/dist/api/types.d.ts.map +1 -1
- package/dist/api/types.js.map +1 -1
- package/dist/memory/AgentMemory.d.ts +2 -1
- package/dist/memory/AgentMemory.d.ts.map +1 -1
- package/dist/memory/AgentMemory.js +1 -1
- package/dist/memory/AgentMemory.js.map +1 -1
- package/dist/memory/CognitiveMemoryManager.d.ts.map +1 -1
- package/dist/memory/CognitiveMemoryManager.js +7 -2
- package/dist/memory/CognitiveMemoryManager.js.map +1 -1
- package/dist/memory/facade/Memory.d.ts.map +1 -1
- package/dist/memory/facade/Memory.js +6 -9
- package/dist/memory/facade/Memory.js.map +1 -1
- package/dist/memory/store/MemoryStore.d.ts +9 -0
- package/dist/memory/store/MemoryStore.d.ts.map +1 -1
- package/dist/memory/store/MemoryStore.js +66 -6
- package/dist/memory/store/MemoryStore.js.map +1 -1
- package/dist/memory/store/SqliteMemoryGraph.d.ts.map +1 -1
- package/dist/memory/store/SqliteMemoryGraph.js +27 -13
- package/dist/memory/store/SqliteMemoryGraph.js.map +1 -1
- package/dist/speech/FallbackProxy.d.ts +194 -41
- package/dist/speech/FallbackProxy.d.ts.map +1 -1
- package/dist/speech/FallbackProxy.js +155 -32
- package/dist/speech/FallbackProxy.js.map +1 -1
- package/dist/speech/SpeechProviderResolver.d.ts +278 -36
- package/dist/speech/SpeechProviderResolver.d.ts.map +1 -1
- package/dist/speech/SpeechProviderResolver.js +306 -40
- package/dist/speech/SpeechProviderResolver.js.map +1 -1
- package/dist/speech/providers/AssemblyAISTTProvider.d.ts +119 -19
- package/dist/speech/providers/AssemblyAISTTProvider.d.ts.map +1 -1
- package/dist/speech/providers/AssemblyAISTTProvider.js +153 -25
- package/dist/speech/providers/AssemblyAISTTProvider.js.map +1 -1
- package/dist/speech/providers/AzureSpeechSTTProvider.d.ts +121 -17
- package/dist/speech/providers/AzureSpeechSTTProvider.d.ts.map +1 -1
- package/dist/speech/providers/AzureSpeechSTTProvider.js +122 -14
- package/dist/speech/providers/AzureSpeechSTTProvider.js.map +1 -1
- package/dist/speech/providers/AzureSpeechTTSProvider.d.ts +130 -15
- package/dist/speech/providers/AzureSpeechTTSProvider.d.ts.map +1 -1
- package/dist/speech/providers/AzureSpeechTTSProvider.js +163 -18
- package/dist/speech/providers/AzureSpeechTTSProvider.js.map +1 -1
- package/dist/speech/providers/BuiltInAdaptiveVadProvider.d.ts +159 -0
- package/dist/speech/providers/BuiltInAdaptiveVadProvider.d.ts.map +1 -1
- package/dist/speech/providers/BuiltInAdaptiveVadProvider.js +119 -0
- package/dist/speech/providers/BuiltInAdaptiveVadProvider.js.map +1 -1
- package/dist/speech/providers/DeepgramBatchSTTProvider.d.ts +102 -16
- package/dist/speech/providers/DeepgramBatchSTTProvider.d.ts.map +1 -1
- package/dist/speech/providers/DeepgramBatchSTTProvider.js +108 -13
- package/dist/speech/providers/DeepgramBatchSTTProvider.js.map +1 -1
- package/dist/speech/providers/ElevenLabsTextToSpeechProvider.d.ts +149 -0
- package/dist/speech/providers/ElevenLabsTextToSpeechProvider.d.ts.map +1 -1
- package/dist/speech/providers/ElevenLabsTextToSpeechProvider.js +137 -2
- package/dist/speech/providers/ElevenLabsTextToSpeechProvider.js.map +1 -1
- package/dist/speech/providers/OpenAITextToSpeechProvider.d.ts +125 -0
- package/dist/speech/providers/OpenAITextToSpeechProvider.d.ts.map +1 -1
- package/dist/speech/providers/OpenAITextToSpeechProvider.js +128 -4
- package/dist/speech/providers/OpenAITextToSpeechProvider.js.map +1 -1
- package/dist/speech/providers/OpenAIWhisperSpeechToTextProvider.d.ts +110 -0
- package/dist/speech/providers/OpenAIWhisperSpeechToTextProvider.d.ts.map +1 -1
- package/dist/speech/providers/OpenAIWhisperSpeechToTextProvider.js +115 -0
- package/dist/speech/providers/OpenAIWhisperSpeechToTextProvider.js.map +1 -1
- package/dist/voice/CallManager.d.ts.map +1 -1
- package/dist/voice/CallManager.js +9 -1
- package/dist/voice/CallManager.js.map +1 -1
- package/dist/voice/MediaStreamParser.d.ts +115 -6
- package/dist/voice/MediaStreamParser.d.ts.map +1 -1
- package/dist/voice/MediaStreamParser.js +44 -0
- package/dist/voice/MediaStreamParser.js.map +1 -1
- package/dist/voice/TelephonyStreamTransport.d.ts +112 -20
- package/dist/voice/TelephonyStreamTransport.d.ts.map +1 -1
- package/dist/voice/TelephonyStreamTransport.js +136 -30
- package/dist/voice/TelephonyStreamTransport.js.map +1 -1
- package/dist/voice/parsers/PlivoMediaStreamParser.d.ts +64 -6
- package/dist/voice/parsers/PlivoMediaStreamParser.d.ts.map +1 -1
- package/dist/voice/parsers/PlivoMediaStreamParser.js +67 -6
- package/dist/voice/parsers/PlivoMediaStreamParser.js.map +1 -1
- package/dist/voice/parsers/TelnyxMediaStreamParser.d.ts +55 -8
- package/dist/voice/parsers/TelnyxMediaStreamParser.d.ts.map +1 -1
- package/dist/voice/parsers/TelnyxMediaStreamParser.js +60 -9
- package/dist/voice/parsers/TelnyxMediaStreamParser.js.map +1 -1
- package/dist/voice/parsers/TwilioMediaStreamParser.d.ts +73 -11
- package/dist/voice/parsers/TwilioMediaStreamParser.d.ts.map +1 -1
- package/dist/voice/parsers/TwilioMediaStreamParser.js +81 -12
- package/dist/voice/parsers/TwilioMediaStreamParser.js.map +1 -1
- package/dist/voice/providers/plivo.d.ts +108 -12
- package/dist/voice/providers/plivo.d.ts.map +1 -1
- package/dist/voice/providers/plivo.js +106 -9
- package/dist/voice/providers/plivo.js.map +1 -1
- package/dist/voice/providers/telnyx.d.ts +110 -20
- package/dist/voice/providers/telnyx.d.ts.map +1 -1
- package/dist/voice/providers/telnyx.js +111 -20
- package/dist/voice/providers/telnyx.js.map +1 -1
- package/dist/voice/providers/twilio.d.ts +91 -13
- package/dist/voice/providers/twilio.d.ts.map +1 -1
- package/dist/voice/providers/twilio.js +94 -14
- package/dist/voice/providers/twilio.js.map +1 -1
- package/dist/voice/twiml.d.ts +70 -12
- package/dist/voice/twiml.d.ts.map +1 -1
- package/dist/voice/twiml.js +70 -12
- package/dist/voice/twiml.js.map +1 -1
- package/dist/voice/types.d.ts +142 -15
- package/dist/voice/types.d.ts.map +1 -1
- package/dist/voice/types.js +34 -3
- package/dist/voice/types.js.map +1 -1
- package/package.json +1 -1
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"TelnyxMediaStreamParser.d.ts","sourceRoot":"","sources":["../../../src/voice/parsers/TelnyxMediaStreamParser.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,iBAAiB,EAAE,mBAAmB,EAAE,MAAM,yBAAyB,CAAC;AAEtF;;;;;;;;;;;;;;GAcG;AACH,qBAAa,uBAAwB,YAAW,iBAAiB;IAC/D
|
|
1
|
+
{"version":3,"file":"TelnyxMediaStreamParser.d.ts","sourceRoot":"","sources":["../../../src/voice/parsers/TelnyxMediaStreamParser.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GAoCG;AAEH,OAAO,KAAK,EAAE,iBAAiB,EAAE,mBAAmB,EAAE,MAAM,yBAAyB,CAAC;AAEtF;;;;;;;;;;;;;;GAcG;AACH,qBAAa,uBAAwB,YAAW,iBAAiB;IAC/D;;;;;;;;;;;;;;;;;OAiBG;IACH,aAAa,CAAC,IAAI,EAAE,MAAM,GAAG,MAAM,GAAG,mBAAmB,GAAG,IAAI;IA6DhE;;;;;;;;;;;OAWG;IACH,cAAc,CAAC,KAAK,EAAE,MAAM,EAAE,UAAU,EAAE,MAAM,GAAG,MAAM;IAIzD;;;;;;;;OAQG;IACH,eAAe,CAAC,UAAU,EAAE,MAAM,GAAG,IAAI;CAG1C"}
|
|
@@ -1,14 +1,51 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* @fileoverview Telnyx media stream WebSocket parser.
|
|
3
|
+
*
|
|
4
|
+
* ## Telnyx's asymmetric protocol
|
|
5
|
+
*
|
|
6
|
+
* Telnyx uses a fundamentally different approach than Twilio for inbound vs.
|
|
7
|
+
* outbound audio on the media stream WebSocket:
|
|
8
|
+
*
|
|
9
|
+
* - **Inbound** (phone -> server): JSON-encoded messages with `event`, `stream_id`,
|
|
10
|
+
* and `media.chunk` (base64 mu-law audio) fields.
|
|
11
|
+
* - **Outbound** (server -> phone): **Raw binary** WebSocket frames containing
|
|
12
|
+
* mu-law PCM bytes directly, with no JSON envelope whatsoever.
|
|
13
|
+
*
|
|
14
|
+
* This asymmetry means {@link formatOutgoing} returns the `Buffer` unchanged,
|
|
15
|
+
* while {@link parseIncoming} parses JSON and base64-decodes the audio payload.
|
|
16
|
+
*
|
|
17
|
+
* ## Field name mapping
|
|
18
|
+
*
|
|
19
|
+
* Telnyx uses snake_case field names that differ from Twilio's conventions.
|
|
20
|
+
* This parser normalises them to the shared {@link MediaStreamIncoming} shape:
|
|
21
|
+
*
|
|
22
|
+
* | Telnyx field | Normalised field |
|
|
23
|
+
* |----------------------|-------------------|
|
|
24
|
+
* | `stream_id` | `streamSid` |
|
|
25
|
+
* | `call_control_id` | `callSid` |
|
|
26
|
+
* | `media.chunk` | `payload` (Buffer)|
|
|
27
|
+
* | `media.track` | (used for filtering, not emitted) |
|
|
28
|
+
*
|
|
29
|
+
* ## DTMF limitation
|
|
30
|
+
*
|
|
31
|
+
* Telnyx does NOT deliver DTMF events over the media stream WebSocket.
|
|
32
|
+
* DTMF key-presses arrive as `call.dtmf.received` HTTP webhook events and
|
|
33
|
+
* must be handled by {@link TelnyxVoiceProvider.parseWebhookEvent} instead.
|
|
34
|
+
*
|
|
35
|
+
* @see {@link https://developers.telnyx.com/docs/voice/media-streaming}
|
|
36
|
+
* @module @framers/agentos/voice/parsers/TelnyxMediaStreamParser
|
|
37
|
+
*/
|
|
1
38
|
/**
|
|
2
39
|
* Parses the Telnyx media stream WebSocket protocol.
|
|
3
40
|
*
|
|
4
41
|
* Telnyx sends JSON-encoded messages for stream lifecycle events (`start`,
|
|
5
|
-
* `stop`) and audio chunks (`media`).
|
|
6
|
-
* DTMF events over the media stream WebSocket
|
|
42
|
+
* `stop`) and audio chunks (`media`). Unlike Twilio, Telnyx does NOT deliver
|
|
43
|
+
* DTMF events over the media stream WebSocket -- those arrive as HTTP webhooks
|
|
7
44
|
* to a separate endpoint and must be handled outside this parser.
|
|
8
45
|
*
|
|
9
46
|
* Outgoing audio is sent as a **raw binary Buffer** (mu-law PCM bytes without
|
|
10
47
|
* any JSON envelope) because Telnyx accepts unframed binary WebSocket frames
|
|
11
|
-
* directly.
|
|
48
|
+
* directly. No explicit connection acknowledgment is needed after the
|
|
12
49
|
* handshake.
|
|
13
50
|
*
|
|
14
51
|
* @see {@link https://developers.telnyx.com/docs/voice/media-streaming}
|
|
@@ -18,11 +55,15 @@ export class TelnyxMediaStreamParser {
|
|
|
18
55
|
* Parse a raw WebSocket frame from Telnyx's media stream.
|
|
19
56
|
*
|
|
20
57
|
* Supported Telnyx event types:
|
|
21
|
-
* - `start`
|
|
58
|
+
* - `start` -- stream established; `stream_id` maps to `streamSid`,
|
|
22
59
|
* `call_control_id` maps to `callSid`.
|
|
23
|
-
* - `media`
|
|
24
|
-
* bytes; only `inbound` track frames are returned
|
|
25
|
-
*
|
|
60
|
+
* - `media` -- audio chunk; `media.chunk` field contains base64-encoded mu-law
|
|
61
|
+
* bytes; only `inbound` track frames are returned (outbound echoes are
|
|
62
|
+
* discarded to prevent feedback loops).
|
|
63
|
+
* - `stop` -- stream ended (call terminated or stream explicitly closed).
|
|
64
|
+
*
|
|
65
|
+
* Any other event type (e.g., future Telnyx additions, DTMF attempts) is
|
|
66
|
+
* silently dropped by returning `null`.
|
|
26
67
|
*
|
|
27
68
|
* @param data - Raw WebSocket frame payload (JSON string or Buffer from Telnyx).
|
|
28
69
|
* @returns Normalised {@link MediaStreamIncoming} event, or `null` for
|
|
@@ -38,12 +79,15 @@ export class TelnyxMediaStreamParser {
|
|
|
38
79
|
return null;
|
|
39
80
|
}
|
|
40
81
|
const event = msg['event'];
|
|
82
|
+
// Telnyx uses `stream_id` where Twilio uses `streamSid`.
|
|
41
83
|
const streamSid = msg['stream_id'];
|
|
42
84
|
if (!event || !streamSid) {
|
|
43
85
|
return null;
|
|
44
86
|
}
|
|
45
87
|
switch (event) {
|
|
46
88
|
case 'start': {
|
|
89
|
+
// Telnyx uses `call_control_id` as the call-leg identifier,
|
|
90
|
+
// equivalent to Twilio's `callSid`.
|
|
47
91
|
const callSid = msg['call_control_id'] ?? '';
|
|
48
92
|
const result = {
|
|
49
93
|
type: 'start',
|
|
@@ -56,10 +100,11 @@ export class TelnyxMediaStreamParser {
|
|
|
56
100
|
const media = msg['media'];
|
|
57
101
|
if (!media)
|
|
58
102
|
return null;
|
|
59
|
-
// Ignore outbound audio echoes from Telnyx.
|
|
103
|
+
// Ignore outbound audio echoes from Telnyx to prevent feedback.
|
|
60
104
|
const track = media['track'];
|
|
61
105
|
if (track === 'outbound')
|
|
62
106
|
return null;
|
|
107
|
+
// Telnyx names its audio payload field `chunk` (not `payload` like Twilio).
|
|
63
108
|
const chunk = media['chunk'];
|
|
64
109
|
if (!chunk)
|
|
65
110
|
return null;
|
|
@@ -81,7 +126,9 @@ export class TelnyxMediaStreamParser {
|
|
|
81
126
|
/**
|
|
82
127
|
* Encode mu-law audio for transmission back to Telnyx.
|
|
83
128
|
*
|
|
84
|
-
* Telnyx accepts raw binary WebSocket frames
|
|
129
|
+
* Telnyx accepts raw binary WebSocket frames -- no JSON wrapping is needed.
|
|
130
|
+
* This is the key asymmetry in Telnyx's protocol: inbound is JSON, outbound
|
|
131
|
+
* is raw binary.
|
|
85
132
|
*
|
|
86
133
|
* @param audio - Raw mu-law PCM bytes to send to the caller.
|
|
87
134
|
* @param _streamSid - Unused by Telnyx binary framing (accepted for interface
|
|
@@ -94,6 +141,10 @@ export class TelnyxMediaStreamParser {
|
|
|
94
141
|
/**
|
|
95
142
|
* No explicit connection acknowledgment is required by Telnyx.
|
|
96
143
|
*
|
|
144
|
+
* Unlike Twilio, Telnyx does not need a `connected` handshake message
|
|
145
|
+
* before it starts sending media events.
|
|
146
|
+
*
|
|
147
|
+
* @param _streamSid - Unused (accepted for interface parity).
|
|
97
148
|
* @returns Always `null`.
|
|
98
149
|
*/
|
|
99
150
|
formatConnected(_streamSid) {
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"TelnyxMediaStreamParser.js","sourceRoot":"","sources":["../../../src/voice/parsers/TelnyxMediaStreamParser.ts"],"names":[],"mappings":"
|
|
1
|
+
{"version":3,"file":"TelnyxMediaStreamParser.js","sourceRoot":"","sources":["../../../src/voice/parsers/TelnyxMediaStreamParser.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GAoCG;AAIH;;;;;;;;;;;;;;GAcG;AACH,MAAM,OAAO,uBAAuB;IAClC;;;;;;;;;;;;;;;;;OAiBG;IACH,aAAa,CAAC,IAAqB;QACjC,MAAM,GAAG,GAAG,OAAO,IAAI,KAAK,QAAQ,CAAC,CAAC,CAAC,IAAI,CAAC,CAAC,CAAC,IAAI,CAAC,QAAQ,CAAC,MAAM,CAAC,CAAC;QAEpE,IAAI,GAA4B,CAAC;QACjC,IAAI,CAAC;YACH,GAAG,GAAG,IAAI,CAAC,KAAK,CAAC,GAAG,CAA4B,CAAC;QACnD,CAAC;QAAC,MAAM,CAAC;YACP,OAAO,IAAI,CAAC;QACd,CAAC;QAED,MAAM,KAAK,GAAG,GAAG,CAAC,OAAO,CAAuB,CAAC;QACjD,yDAAyD;QACzD,MAAM,SAAS,GAAG,GAAG,CAAC,WAAW,CAAuB,CAAC;QAEzD,IAAI,CAAC,KAAK,IAAI,CAAC,SAAS,EAAE,CAAC;YACzB,OAAO,IAAI,CAAC;QACd,CAAC;QAED,QAAQ,KAAK,EAAE,CAAC;YACd,KAAK,OAAO,CAAC,CAAC,CAAC;gBACb,4DAA4D;gBAC5D,oCAAoC;gBACpC,MAAM,OAAO,GAAI,GAAG,CAAC,iBAAiB,CAAwB,IAAI,EAAE,CAAC;gBACrE,MAAM,MAAM,GAAwB;oBAClC,IAAI,EAAE,OAAO;oBACb,SAAS;oBACT,OAAO;iBACR,CAAC;gBACF,OAAO,MAAM,CAAC;YAChB,CAAC;YAED,KAAK,OAAO,CAAC,CAAC,CAAC;gBACb,MAAM,KAAK,GAAG,GAAG,CAAC,OAAO,CAAwC,CAAC;gBAClE,IAAI,CAAC,KAAK;oBAAE,OAAO,IAAI,CAAC;gBAExB,gEAAgE;gBAChE,MAAM,KAAK,GAAG,KAAK,CAAC,OAAO,CAAuB,CAAC;gBACnD,IAAI,KAAK,KAAK,UAAU;oBAAE,OAAO,IAAI,CAAC;gBAEtC,4EAA4E;gBAC5E,MAAM,KAAK,GAAG,KAAK,CAAC,OAAO,CAAuB,CAAC;gBACnD,IAAI,CAAC,KAAK;oBAAE,OAAO,IAAI,CAAC;gBAExB,MAAM,MAAM,GAAwB;oBAClC,IAAI,EAAE,OAAO;oBACb,OAAO,EAAE,MAAM,CAAC,IAAI,CAAC,KAAK,EAAE,QAAQ,CAAC;oBACrC,SAAS;iBACV,CAAC;gBACF,OAAO,MAAM,CAAC;YAChB,CAAC;YAED,KAAK,MAAM,CAAC,CAAC,CAAC;gBACZ,MAAM,MAAM,GAAwB,EAAE,IAAI,EAAE,MAAM,EAAE,SAAS,EAAE,CAAC;gBAChE,OAAO,MAAM,CAAC;YAChB,CAAC;YAED;gBACE,OAAO,IAAI,CAAC;QAChB,CAAC;IACH,CAAC;IAED;;;;;;;;;;;OAWG;IACH,cAAc,CAAC,KAAa,EAAE,UAAkB;QAC9C,OAAO,KAAK,CAAC;IACf,CAAC;IAED;;;;;;;;OAQG;IACH,eAAe,CAAC,UAAkB;QAChC,OAAO,IAAI,CAAC;IACd,CAAC;CACF"}
|
|
@@ -1,10 +1,64 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* @fileoverview Twilio `<Connect><Stream>` WebSocket media stream parser.
|
|
3
|
+
*
|
|
4
|
+
* ## Twilio media stream protocol
|
|
5
|
+
*
|
|
6
|
+
* When a Twilio call executes the TwiML `<Connect><Stream url="wss://..." />`,
|
|
7
|
+
* Twilio opens a WebSocket to the specified URL and sends **all messages as
|
|
8
|
+
* JSON-encoded strings** (never raw binary). Each message has an `event` field
|
|
9
|
+
* and a `streamSid` field that together identify the event type and stream.
|
|
10
|
+
*
|
|
11
|
+
* ### Inbound JSON message shapes
|
|
12
|
+
*
|
|
13
|
+
* ```
|
|
14
|
+
* ┌─────────────────────────────────────────────────────────────────────┐
|
|
15
|
+
* │ event: "start" │
|
|
16
|
+
* │ streamSid: "MZxxx" │
|
|
17
|
+
* │ start: { callSid, accountSid, mediaFormat: { encoding, ... } } │
|
|
18
|
+
* ├─────────────────────────────────────────────────────────────────────┤
|
|
19
|
+
* │ event: "media" │
|
|
20
|
+
* │ streamSid: "MZxxx" │
|
|
21
|
+
* │ media: { track: "inbound"|"outbound", payload: "<base64>" } │
|
|
22
|
+
* │ sequenceNumber: 42 │
|
|
23
|
+
* ├─────────────────────────────────────────────────────────────────────┤
|
|
24
|
+
* │ event: "dtmf" │
|
|
25
|
+
* │ streamSid: "MZxxx" │
|
|
26
|
+
* │ dtmf: { digit: "5", duration: 500 } │
|
|
27
|
+
* ├─────────────────────────────────────────────────────────────────────┤
|
|
28
|
+
* │ event: "mark" │
|
|
29
|
+
* │ streamSid: "MZxxx" │
|
|
30
|
+
* │ mark: { name: "utterance-done" } │
|
|
31
|
+
* ├─────────────────────────────────────────────────────────────────────┤
|
|
32
|
+
* │ event: "stop" │
|
|
33
|
+
* │ streamSid: "MZxxx" │
|
|
34
|
+
* └─────────────────────────────────────────────────────────────────────┘
|
|
35
|
+
* ```
|
|
36
|
+
*
|
|
37
|
+
* ### Outbound audio format
|
|
38
|
+
*
|
|
39
|
+
* Audio sent back to Twilio must be wrapped in a JSON `media` envelope:
|
|
40
|
+
* ```json
|
|
41
|
+
* { "event": "media", "streamSid": "MZxxx", "media": { "payload": "<base64>" } }
|
|
42
|
+
* ```
|
|
43
|
+
*
|
|
44
|
+
* ### Connection acknowledgment
|
|
45
|
+
*
|
|
46
|
+
* Immediately after the WebSocket handshake, the server must send:
|
|
47
|
+
* ```json
|
|
48
|
+
* { "event": "connected", "protocol": "Call", "version": "1.0.0" }
|
|
49
|
+
* ```
|
|
50
|
+
* This tells Twilio the listener is ready to receive media.
|
|
51
|
+
*
|
|
52
|
+
* @see {@link https://www.twilio.com/docs/voice/twiml/stream}
|
|
53
|
+
* @module @framers/agentos/voice/parsers/TwilioMediaStreamParser
|
|
54
|
+
*/
|
|
1
55
|
import type { MediaStreamParser, MediaStreamIncoming } from '../MediaStreamParser.js';
|
|
2
56
|
/**
|
|
3
57
|
* Parses the Twilio `<Connect><Stream>` WebSocket media stream protocol.
|
|
4
58
|
*
|
|
5
|
-
* Twilio sends all messages as JSON-encoded strings.
|
|
59
|
+
* Twilio sends all messages as JSON-encoded strings. Outbound audio is
|
|
6
60
|
* wrapped in the same JSON envelope so Twilio can associate it with the
|
|
7
|
-
* correct stream.
|
|
61
|
+
* correct stream. An explicit `connected` acknowledgment is sent once
|
|
8
62
|
* immediately after the WebSocket handshake to signal that the listener is
|
|
9
63
|
* ready to receive media.
|
|
10
64
|
*
|
|
@@ -15,11 +69,15 @@ export declare class TwilioMediaStreamParser implements MediaStreamParser {
|
|
|
15
69
|
* Parse a raw WebSocket frame from Twilio's media stream.
|
|
16
70
|
*
|
|
17
71
|
* Supported Twilio event types:
|
|
18
|
-
* - `start`
|
|
19
|
-
* - `media`
|
|
20
|
-
*
|
|
21
|
-
* - `
|
|
22
|
-
* - `
|
|
72
|
+
* - `start` -- stream established, includes callSid and media format metadata.
|
|
73
|
+
* - `media` -- audio chunk (inbound track only; outbound echoes are discarded
|
|
74
|
+
* to prevent feedback loops).
|
|
75
|
+
* - `dtmf` -- DTMF keypress detected on the audio stream.
|
|
76
|
+
* - `stop` -- stream ended (call hangup or stream disconnect).
|
|
77
|
+
* - `mark` -- named synchronisation marker confirming playback reached a point.
|
|
78
|
+
*
|
|
79
|
+
* Messages with missing `event` or `streamSid` fields, malformed JSON,
|
|
80
|
+
* or unrecognised event types are silently dropped (return `null`).
|
|
23
81
|
*
|
|
24
82
|
* @param data - Raw WebSocket frame payload (always a JSON string from Twilio).
|
|
25
83
|
* @returns Normalised {@link MediaStreamIncoming} event, or `null` for
|
|
@@ -30,20 +88,24 @@ export declare class TwilioMediaStreamParser implements MediaStreamParser {
|
|
|
30
88
|
* Encode mu-law audio for transmission back to the Twilio stream.
|
|
31
89
|
*
|
|
32
90
|
* Twilio requires base64-encoded audio wrapped in a JSON `media` envelope
|
|
33
|
-
* so it can route the audio to the correct stream
|
|
91
|
+
* so it can route the audio to the correct stream by `streamSid`.
|
|
34
92
|
*
|
|
35
93
|
* @param audio - Raw mu-law PCM bytes to send to the caller.
|
|
36
94
|
* @param streamSid - The stream identifier to include in the envelope.
|
|
37
|
-
* @returns JSON string conforming to the Twilio media-out envelope format
|
|
95
|
+
* @returns JSON string conforming to the Twilio media-out envelope format:
|
|
96
|
+
* `{ event: 'media', streamSid: '...', media: { payload: '<base64>' } }`
|
|
38
97
|
*/
|
|
39
98
|
formatOutgoing(audio: Buffer, streamSid: string): string;
|
|
40
99
|
/**
|
|
41
100
|
* Generate the initial `connected` acknowledgment expected by Twilio
|
|
42
101
|
* immediately after the WebSocket connection is established.
|
|
43
102
|
*
|
|
44
|
-
*
|
|
103
|
+
* Without this message, Twilio waits indefinitely for a response and
|
|
104
|
+
* eventually times out the stream connection.
|
|
105
|
+
*
|
|
106
|
+
* @param _streamSid - Unused -- Twilio does not require the stream ID in the
|
|
45
107
|
* `connected` message, but the parameter is accepted for interface parity.
|
|
46
|
-
* @returns JSON string
|
|
108
|
+
* @returns JSON string: `{ event: 'connected', protocol: 'Call', version: '1.0.0' }`
|
|
47
109
|
*/
|
|
48
110
|
formatConnected(_streamSid: string): string;
|
|
49
111
|
}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"TwilioMediaStreamParser.d.ts","sourceRoot":"","sources":["../../../src/voice/parsers/TwilioMediaStreamParser.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,iBAAiB,EAAE,mBAAmB,EAAE,MAAM,yBAAyB,CAAC;AAEtF;;;;;;;;;;GAUG;AACH,qBAAa,uBAAwB,YAAW,iBAAiB;IAC/D
|
|
1
|
+
{"version":3,"file":"TwilioMediaStreamParser.d.ts","sourceRoot":"","sources":["../../../src/voice/parsers/TwilioMediaStreamParser.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GAqDG;AAEH,OAAO,KAAK,EAAE,iBAAiB,EAAE,mBAAmB,EAAE,MAAM,yBAAyB,CAAC;AAEtF;;;;;;;;;;GAUG;AACH,qBAAa,uBAAwB,YAAW,iBAAiB;IAC/D;;;;;;;;;;;;;;;;;OAiBG;IACH,aAAa,CAAC,IAAI,EAAE,MAAM,GAAG,MAAM,GAAG,mBAAmB,GAAG,IAAI;IAsGhE;;;;;;;;;;OAUG;IACH,cAAc,CAAC,KAAK,EAAE,MAAM,EAAE,SAAS,EAAE,MAAM,GAAG,MAAM;IAQxD;;;;;;;;;;OAUG;IACH,eAAe,CAAC,UAAU,EAAE,MAAM,GAAG,MAAM;CAO5C"}
|
|
@@ -1,9 +1,63 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* @fileoverview Twilio `<Connect><Stream>` WebSocket media stream parser.
|
|
3
|
+
*
|
|
4
|
+
* ## Twilio media stream protocol
|
|
5
|
+
*
|
|
6
|
+
* When a Twilio call executes the TwiML `<Connect><Stream url="wss://..." />`,
|
|
7
|
+
* Twilio opens a WebSocket to the specified URL and sends **all messages as
|
|
8
|
+
* JSON-encoded strings** (never raw binary). Each message has an `event` field
|
|
9
|
+
* and a `streamSid` field that together identify the event type and stream.
|
|
10
|
+
*
|
|
11
|
+
* ### Inbound JSON message shapes
|
|
12
|
+
*
|
|
13
|
+
* ```
|
|
14
|
+
* ┌─────────────────────────────────────────────────────────────────────┐
|
|
15
|
+
* │ event: "start" │
|
|
16
|
+
* │ streamSid: "MZxxx" │
|
|
17
|
+
* │ start: { callSid, accountSid, mediaFormat: { encoding, ... } } │
|
|
18
|
+
* ├─────────────────────────────────────────────────────────────────────┤
|
|
19
|
+
* │ event: "media" │
|
|
20
|
+
* │ streamSid: "MZxxx" │
|
|
21
|
+
* │ media: { track: "inbound"|"outbound", payload: "<base64>" } │
|
|
22
|
+
* │ sequenceNumber: 42 │
|
|
23
|
+
* ├─────────────────────────────────────────────────────────────────────┤
|
|
24
|
+
* │ event: "dtmf" │
|
|
25
|
+
* │ streamSid: "MZxxx" │
|
|
26
|
+
* │ dtmf: { digit: "5", duration: 500 } │
|
|
27
|
+
* ├─────────────────────────────────────────────────────────────────────┤
|
|
28
|
+
* │ event: "mark" │
|
|
29
|
+
* │ streamSid: "MZxxx" │
|
|
30
|
+
* │ mark: { name: "utterance-done" } │
|
|
31
|
+
* ├─────────────────────────────────────────────────────────────────────┤
|
|
32
|
+
* │ event: "stop" │
|
|
33
|
+
* │ streamSid: "MZxxx" │
|
|
34
|
+
* └─────────────────────────────────────────────────────────────────────┘
|
|
35
|
+
* ```
|
|
36
|
+
*
|
|
37
|
+
* ### Outbound audio format
|
|
38
|
+
*
|
|
39
|
+
* Audio sent back to Twilio must be wrapped in a JSON `media` envelope:
|
|
40
|
+
* ```json
|
|
41
|
+
* { "event": "media", "streamSid": "MZxxx", "media": { "payload": "<base64>" } }
|
|
42
|
+
* ```
|
|
43
|
+
*
|
|
44
|
+
* ### Connection acknowledgment
|
|
45
|
+
*
|
|
46
|
+
* Immediately after the WebSocket handshake, the server must send:
|
|
47
|
+
* ```json
|
|
48
|
+
* { "event": "connected", "protocol": "Call", "version": "1.0.0" }
|
|
49
|
+
* ```
|
|
50
|
+
* This tells Twilio the listener is ready to receive media.
|
|
51
|
+
*
|
|
52
|
+
* @see {@link https://www.twilio.com/docs/voice/twiml/stream}
|
|
53
|
+
* @module @framers/agentos/voice/parsers/TwilioMediaStreamParser
|
|
54
|
+
*/
|
|
1
55
|
/**
|
|
2
56
|
* Parses the Twilio `<Connect><Stream>` WebSocket media stream protocol.
|
|
3
57
|
*
|
|
4
|
-
* Twilio sends all messages as JSON-encoded strings.
|
|
58
|
+
* Twilio sends all messages as JSON-encoded strings. Outbound audio is
|
|
5
59
|
* wrapped in the same JSON envelope so Twilio can associate it with the
|
|
6
|
-
* correct stream.
|
|
60
|
+
* correct stream. An explicit `connected` acknowledgment is sent once
|
|
7
61
|
* immediately after the WebSocket handshake to signal that the listener is
|
|
8
62
|
* ready to receive media.
|
|
9
63
|
*
|
|
@@ -14,11 +68,15 @@ export class TwilioMediaStreamParser {
|
|
|
14
68
|
* Parse a raw WebSocket frame from Twilio's media stream.
|
|
15
69
|
*
|
|
16
70
|
* Supported Twilio event types:
|
|
17
|
-
* - `start`
|
|
18
|
-
* - `media`
|
|
19
|
-
*
|
|
20
|
-
* - `
|
|
21
|
-
* - `
|
|
71
|
+
* - `start` -- stream established, includes callSid and media format metadata.
|
|
72
|
+
* - `media` -- audio chunk (inbound track only; outbound echoes are discarded
|
|
73
|
+
* to prevent feedback loops).
|
|
74
|
+
* - `dtmf` -- DTMF keypress detected on the audio stream.
|
|
75
|
+
* - `stop` -- stream ended (call hangup or stream disconnect).
|
|
76
|
+
* - `mark` -- named synchronisation marker confirming playback reached a point.
|
|
77
|
+
*
|
|
78
|
+
* Messages with missing `event` or `streamSid` fields, malformed JSON,
|
|
79
|
+
* or unrecognised event types are silently dropped (return `null`).
|
|
22
80
|
*
|
|
23
81
|
* @param data - Raw WebSocket frame payload (always a JSON string from Twilio).
|
|
24
82
|
* @returns Normalised {@link MediaStreamIncoming} event, or `null` for
|
|
@@ -35,12 +93,14 @@ export class TwilioMediaStreamParser {
|
|
|
35
93
|
}
|
|
36
94
|
const event = msg['event'];
|
|
37
95
|
const streamSid = msg['streamSid'];
|
|
96
|
+
// Both fields are required on every Twilio media stream message.
|
|
38
97
|
if (!event || !streamSid) {
|
|
39
98
|
return null;
|
|
40
99
|
}
|
|
41
100
|
switch (event) {
|
|
42
101
|
case 'start': {
|
|
43
102
|
const startPayload = msg['start'];
|
|
103
|
+
// callSid identifies the Twilio call leg this stream belongs to.
|
|
44
104
|
const callSid = startPayload?.['callSid'] ?? '';
|
|
45
105
|
const result = {
|
|
46
106
|
type: 'start',
|
|
@@ -54,7 +114,9 @@ export class TwilioMediaStreamParser {
|
|
|
54
114
|
const media = msg['media'];
|
|
55
115
|
if (!media)
|
|
56
116
|
return null;
|
|
57
|
-
//
|
|
117
|
+
// Twilio sends both inbound and outbound audio on the same stream.
|
|
118
|
+
// Outbound echoes must be discarded to prevent feedback loops where
|
|
119
|
+
// the agent hears its own TTS output.
|
|
58
120
|
const track = media['track'];
|
|
59
121
|
if (track === 'outbound')
|
|
60
122
|
return null;
|
|
@@ -79,6 +141,7 @@ export class TwilioMediaStreamParser {
|
|
|
79
141
|
const digit = dtmf['digit'];
|
|
80
142
|
if (!digit)
|
|
81
143
|
return null;
|
|
144
|
+
// Twilio reports DTMF key-hold duration in milliseconds.
|
|
82
145
|
const duration = typeof dtmf['duration'] === 'number'
|
|
83
146
|
? dtmf['duration']
|
|
84
147
|
: undefined;
|
|
@@ -105,6 +168,8 @@ export class TwilioMediaStreamParser {
|
|
|
105
168
|
return result;
|
|
106
169
|
}
|
|
107
170
|
default:
|
|
171
|
+
// Twilio may add new event types in the future; silently ignore them
|
|
172
|
+
// rather than throwing so existing deployments remain forward-compatible.
|
|
108
173
|
return null;
|
|
109
174
|
}
|
|
110
175
|
}
|
|
@@ -112,11 +177,12 @@ export class TwilioMediaStreamParser {
|
|
|
112
177
|
* Encode mu-law audio for transmission back to the Twilio stream.
|
|
113
178
|
*
|
|
114
179
|
* Twilio requires base64-encoded audio wrapped in a JSON `media` envelope
|
|
115
|
-
* so it can route the audio to the correct stream
|
|
180
|
+
* so it can route the audio to the correct stream by `streamSid`.
|
|
116
181
|
*
|
|
117
182
|
* @param audio - Raw mu-law PCM bytes to send to the caller.
|
|
118
183
|
* @param streamSid - The stream identifier to include in the envelope.
|
|
119
|
-
* @returns JSON string conforming to the Twilio media-out envelope format
|
|
184
|
+
* @returns JSON string conforming to the Twilio media-out envelope format:
|
|
185
|
+
* `{ event: 'media', streamSid: '...', media: { payload: '<base64>' } }`
|
|
120
186
|
*/
|
|
121
187
|
formatOutgoing(audio, streamSid) {
|
|
122
188
|
return JSON.stringify({
|
|
@@ -129,9 +195,12 @@ export class TwilioMediaStreamParser {
|
|
|
129
195
|
* Generate the initial `connected` acknowledgment expected by Twilio
|
|
130
196
|
* immediately after the WebSocket connection is established.
|
|
131
197
|
*
|
|
132
|
-
*
|
|
198
|
+
* Without this message, Twilio waits indefinitely for a response and
|
|
199
|
+
* eventually times out the stream connection.
|
|
200
|
+
*
|
|
201
|
+
* @param _streamSid - Unused -- Twilio does not require the stream ID in the
|
|
133
202
|
* `connected` message, but the parameter is accepted for interface parity.
|
|
134
|
-
* @returns JSON string
|
|
203
|
+
* @returns JSON string: `{ event: 'connected', protocol: 'Call', version: '1.0.0' }`
|
|
135
204
|
*/
|
|
136
205
|
formatConnected(_streamSid) {
|
|
137
206
|
return JSON.stringify({
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"TwilioMediaStreamParser.js","sourceRoot":"","sources":["../../../src/voice/parsers/TwilioMediaStreamParser.ts"],"names":[],"mappings":"
|
|
1
|
+
{"version":3,"file":"TwilioMediaStreamParser.js","sourceRoot":"","sources":["../../../src/voice/parsers/TwilioMediaStreamParser.ts"],"names":[],"mappings":"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"}
|
|
@@ -2,20 +2,78 @@
|
|
|
2
2
|
* @fileoverview Plivo telephony provider for AgentOS voice calls.
|
|
3
3
|
*
|
|
4
4
|
* Implements {@link IVoiceCallProvider} using the Plivo Voice REST API v1.
|
|
5
|
-
*
|
|
5
|
+
*
|
|
6
|
+
* ## REST API contract
|
|
7
|
+
*
|
|
8
|
+
* | Operation | Method | Endpoint | Body format |
|
|
9
|
+
* |----------------|--------|----------------------------------------------|-------------|
|
|
10
|
+
* | Initiate call | POST | `/v1/Account/{authId}/Call/` | JSON |
|
|
11
|
+
* | Hangup call | DELETE | `/v1/Account/{authId}/Call/{callUuid}/` | (none) |
|
|
12
|
+
* | Play TTS | POST | `/v1/Account/{authId}/Call/{callUuid}/Speak/` | JSON |
|
|
13
|
+
*
|
|
14
|
+
* All requests use HTTP Basic authentication: `Authorization: Basic base64(authId:authToken)`.
|
|
15
|
+
*
|
|
16
|
+
* ### Hangup uses DELETE (not POST)
|
|
17
|
+
*
|
|
18
|
+
* Unlike Twilio (which POSTs `Status=completed`) and Telnyx (which POSTs to
|
|
19
|
+
* an `/actions/hangup` endpoint), Plivo uses the HTTP `DELETE` method on the
|
|
20
|
+
* Call resource to terminate an active call. This is a RESTful design choice
|
|
21
|
+
* where "deleting" the call resource means terminating the call.
|
|
22
|
+
*
|
|
23
|
+
* ## Webhook verification: HMAC-SHA256 + nonce (v3 scheme)
|
|
24
|
+
*
|
|
25
|
+
* Plivo's v3 webhook signature scheme:
|
|
26
|
+
*
|
|
27
|
+
* 1. Plivo generates a random `nonce` and includes it in the
|
|
28
|
+
* `X-Plivo-Signature-V3-Nonce` header.
|
|
29
|
+
* 2. The signed data is: `{fullRequestURL}{nonce}` (concatenated, no separator).
|
|
30
|
+
* 3. Compute `HMAC-SHA256(authToken, signedData)`.
|
|
31
|
+
* 4. Base64-encode the HMAC digest.
|
|
32
|
+
* 5. Compare the result with the `X-Plivo-Signature-V3` header.
|
|
33
|
+
*
|
|
34
|
+
* Note: Unlike Twilio's scheme, Plivo does NOT include the POST body in the
|
|
35
|
+
* signed data -- only the URL and nonce. The nonce prevents replay attacks.
|
|
36
|
+
*
|
|
37
|
+
* ## DTMF via `<GetDigits>` XML pattern
|
|
38
|
+
*
|
|
39
|
+
* Plivo delivers DTMF input through the `<GetDigits>` XML element callback,
|
|
40
|
+
* not through the media stream WebSocket. When a call executes:
|
|
41
|
+
*
|
|
42
|
+
* ```xml
|
|
43
|
+
* <GetDigits action="https://example.com/dtmf" method="POST" timeout="10">
|
|
44
|
+
* <Speak>Press 1 to confirm.</Speak>
|
|
45
|
+
* </GetDigits>
|
|
46
|
+
* ```
|
|
47
|
+
*
|
|
48
|
+
* Plivo POSTs the pressed digits to the `action` URL with a `Digits`
|
|
49
|
+
* parameter in the form-encoded body (e.g., `Digits=1&CallUUID=xxx`).
|
|
50
|
+
*
|
|
51
|
+
* ## Event mapping table
|
|
52
|
+
*
|
|
53
|
+
* | Plivo `CallStatus` | Normalised `kind` |
|
|
54
|
+
* |---------------------|----------------------|
|
|
55
|
+
* | `ringing` | `call-ringing` |
|
|
56
|
+
* | `in-progress` | `call-answered` |
|
|
57
|
+
* | `completed` | `call-completed` |
|
|
58
|
+
* | `busy` | `call-busy` |
|
|
59
|
+
* | `no-answer` | `call-no-answer` |
|
|
60
|
+
* | `failed` | `call-failed` |
|
|
61
|
+
* | (+ `Digits` param) | `call-dtmf` |
|
|
6
62
|
*
|
|
7
63
|
* @module @framers/agentos/voice/providers/plivo
|
|
8
64
|
*/
|
|
9
65
|
import type { IVoiceCallProvider, InitiateCallInput, InitiateCallResult, HangupCallInput, PlayTtsInput } from '../IVoiceCallProvider.js';
|
|
10
66
|
import type { WebhookContext, WebhookVerificationResult, WebhookParseResult } from '../types.js';
|
|
11
|
-
/**
|
|
67
|
+
/**
|
|
68
|
+
* Configuration for {@link PlivoVoiceProvider}.
|
|
69
|
+
*/
|
|
12
70
|
export interface PlivoVoiceProviderConfig {
|
|
13
|
-
/** Plivo Auth ID (account identifier). */
|
|
71
|
+
/** Plivo Auth ID (account identifier, used in API URLs and Basic auth). */
|
|
14
72
|
authId: string;
|
|
15
|
-
/** Plivo Auth Token. */
|
|
73
|
+
/** Plivo Auth Token (used for both API auth and webhook HMAC verification). */
|
|
16
74
|
authToken: string;
|
|
17
75
|
/**
|
|
18
|
-
* Optional fetch override
|
|
76
|
+
* Optional fetch implementation override -- inject a mock in tests.
|
|
19
77
|
* Defaults to the global `fetch`.
|
|
20
78
|
*/
|
|
21
79
|
fetchImpl?: typeof fetch;
|
|
@@ -35,42 +93,80 @@ export interface PlivoVoiceProviderConfig {
|
|
|
35
93
|
* ```
|
|
36
94
|
*/
|
|
37
95
|
export declare class PlivoVoiceProvider implements IVoiceCallProvider {
|
|
96
|
+
/** Provider identifier, always `'plivo'`. */
|
|
38
97
|
readonly name: "plivo";
|
|
98
|
+
/** Immutable configuration snapshot. */
|
|
39
99
|
private readonly config;
|
|
100
|
+
/** Base URL for the Plivo REST API v1. */
|
|
40
101
|
private readonly baseUrl;
|
|
102
|
+
/** Pre-computed `Authorization: Basic ...` header value. */
|
|
41
103
|
private readonly authHeader;
|
|
104
|
+
/** HTTP fetch implementation (injectable for testing). */
|
|
42
105
|
private readonly fetch;
|
|
106
|
+
/**
|
|
107
|
+
* @param config - Plivo credentials and optional overrides.
|
|
108
|
+
*/
|
|
43
109
|
constructor(config: PlivoVoiceProviderConfig);
|
|
44
110
|
/**
|
|
45
111
|
* Verify an incoming Plivo webhook request using HMAC-SHA256 (v3 scheme).
|
|
46
112
|
*
|
|
47
|
-
*
|
|
48
|
-
*
|
|
49
|
-
* the `
|
|
113
|
+
* ## Algorithm (step by step)
|
|
114
|
+
*
|
|
115
|
+
* 1. Extract the `X-Plivo-Signature-V3-Nonce` and `X-Plivo-Signature-V3` headers.
|
|
116
|
+
* 2. Build the signed data string: `{fullRequestURL}{nonce}`.
|
|
117
|
+
* Note: the POST body is NOT included in the signed data (unlike Twilio).
|
|
118
|
+
* 3. Compute `HMAC-SHA256(authToken, signedData)`.
|
|
119
|
+
* 4. Base64-encode the digest.
|
|
120
|
+
* 5. Compare with the `X-Plivo-Signature-V3` header.
|
|
121
|
+
*
|
|
122
|
+
* @param ctx - Raw webhook request context.
|
|
123
|
+
* @returns Verification result with `valid: true` if the signature matches.
|
|
50
124
|
*/
|
|
51
125
|
verifyWebhook(ctx: WebhookContext): WebhookVerificationResult;
|
|
52
126
|
/**
|
|
53
127
|
* Parse a Plivo webhook body into normalized {@link NormalizedCallEvent}s.
|
|
54
128
|
*
|
|
55
|
-
*
|
|
56
|
-
*
|
|
129
|
+
* Plivo sends most webhooks with URL-encoded bodies, but some callbacks
|
|
130
|
+
* may arrive as JSON. This parser handles both formats by inspecting
|
|
131
|
+
* whether the body starts with `{` (JSON) or not (form-encoded).
|
|
132
|
+
*
|
|
133
|
+
* Plivo uses two naming conventions for the same fields:
|
|
134
|
+
* - PascalCase (`CallUUID`, `CallStatus`, `Digits`) in URL callbacks.
|
|
135
|
+
* - snake_case (`call_uuid`, `call_status`) in some API responses.
|
|
136
|
+
* Both are checked for maximum compatibility.
|
|
137
|
+
*
|
|
138
|
+
* @param ctx - Raw webhook request context.
|
|
139
|
+
* @returns Parsed result containing zero or more normalized events.
|
|
57
140
|
*/
|
|
58
141
|
parseWebhookEvent(ctx: WebhookContext): WebhookParseResult;
|
|
59
142
|
/**
|
|
60
143
|
* Initiate an outbound call via the Plivo Call API.
|
|
61
144
|
*
|
|
62
|
-
* POSTs a JSON body to `/Account/{authId}/Call/` with the caller, callee,
|
|
145
|
+
* POSTs a JSON body to `/v1/Account/{authId}/Call/` with the caller, callee,
|
|
63
146
|
* and answer URL. Returns the `request_uuid` as the provider call ID.
|
|
147
|
+
*
|
|
148
|
+
* @param input - Call initiation parameters (from/to numbers, webhook URL).
|
|
149
|
+
* @returns Result containing the Plivo `request_uuid` on success.
|
|
150
|
+
* @throws Never throws; returns `{ success: false, error: '...' }` on failure.
|
|
64
151
|
*/
|
|
65
152
|
initiateCall(input: InitiateCallInput): Promise<InitiateCallResult>;
|
|
66
153
|
/**
|
|
67
154
|
* Hang up an active call using the Plivo Call DELETE endpoint.
|
|
155
|
+
*
|
|
156
|
+
* Plivo uses HTTP `DELETE` to terminate a call (unlike Twilio's POST with
|
|
157
|
+
* `Status=completed` or Telnyx's POST to `/actions/hangup`). This is a
|
|
158
|
+
* RESTful convention where deleting the call resource ends the call.
|
|
159
|
+
*
|
|
160
|
+
* @param input - Contains the Plivo `call_uuid` to hang up.
|
|
68
161
|
*/
|
|
69
162
|
hangupCall(input: HangupCallInput): Promise<void>;
|
|
70
163
|
/**
|
|
71
164
|
* Speak text into a live call using the Plivo Speak API.
|
|
72
165
|
*
|
|
73
|
-
*
|
|
166
|
+
* POSTs a JSON body to `/v1/Account/{authId}/Call/{callUuid}/Speak/`
|
|
167
|
+
* with the text, voice (default `'WOMAN'`), and language (default `'en-US'`).
|
|
168
|
+
*
|
|
169
|
+
* @param input - TTS parameters (text, optional voice, call ID).
|
|
74
170
|
*/
|
|
75
171
|
playTts(input: PlayTtsInput): Promise<void>;
|
|
76
172
|
}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"plivo.d.ts","sourceRoot":"","sources":["../../../src/voice/providers/plivo.ts"],"names":[],"mappings":"AAAA
|
|
1
|
+
{"version":3,"file":"plivo.d.ts","sourceRoot":"","sources":["../../../src/voice/providers/plivo.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GA+DG;AAIH,OAAO,KAAK,EACV,kBAAkB,EAClB,iBAAiB,EACjB,kBAAkB,EAClB,eAAe,EACf,YAAY,EACb,MAAM,0BAA0B,CAAC;AAElC,OAAO,KAAK,EACV,cAAc,EACd,yBAAyB,EACzB,kBAAkB,EAEnB,MAAM,aAAa,CAAC;AAMrB;;GAEG;AACH,MAAM,WAAW,wBAAwB;IACvC,2EAA2E;IAC3E,MAAM,EAAE,MAAM,CAAC;IACf,+EAA+E;IAC/E,SAAS,EAAE,MAAM,CAAC;IAClB;;;OAGG;IACH,SAAS,CAAC,EAAE,OAAO,KAAK,CAAC;CAC1B;AAMD;;;;;;;;;;;;;GAaG;AACH,qBAAa,kBAAmB,YAAW,kBAAkB;IAC3D,6CAA6C;IAC7C,QAAQ,CAAC,IAAI,EAAG,OAAO,CAAU;IAEjC,wCAAwC;IACxC,OAAO,CAAC,QAAQ,CAAC,MAAM,CAA2B;IAElD,0CAA0C;IAC1C,OAAO,CAAC,QAAQ,CAAC,OAAO,CAAS;IAEjC,4DAA4D;IAC5D,OAAO,CAAC,QAAQ,CAAC,UAAU,CAAS;IAEpC,0DAA0D;IAC1D,OAAO,CAAC,QAAQ,CAAC,KAAK,CAAe;IAErC;;OAEG;gBACS,MAAM,EAAE,wBAAwB;IAW5C;;;;;;;;;;;;;;OAcG;IACH,aAAa,CAAC,GAAG,EAAE,cAAc,GAAG,yBAAyB;IAoB7D;;;;;;;;;;;;;;OAcG;IACH,iBAAiB,CAAC,GAAG,EAAE,cAAc,GAAG,kBAAkB;IAkE1D;;;;;;;;;OASG;IACG,YAAY,CAAC,KAAK,EAAE,iBAAiB,GAAG,OAAO,CAAC,kBAAkB,CAAC;IA0BzE;;;;;;;;OAQG;IACG,UAAU,CAAC,KAAK,EAAE,eAAe,GAAG,OAAO,CAAC,IAAI,CAAC;IAWvD;;;;;;;OAOG;IACG,OAAO,CAAC,KAAK,EAAE,YAAY,GAAG,OAAO,CAAC,IAAI,CAAC;CAgBlD"}
|