@framers/agentos 0.1.111 → 0.1.113
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/strategies/debate.d.ts +12 -1
- package/dist/api/strategies/debate.d.ts.map +1 -1
- package/dist/api/strategies/debate.js +41 -5
- package/dist/api/strategies/debate.js.map +1 -1
- package/dist/api/strategies/hierarchical.d.ts +15 -1
- package/dist/api/strategies/hierarchical.d.ts.map +1 -1
- package/dist/api/strategies/hierarchical.js +51 -7
- package/dist/api/strategies/hierarchical.js.map +1 -1
- package/dist/api/strategies/index.d.ts +26 -4
- package/dist/api/strategies/index.d.ts.map +1 -1
- package/dist/api/strategies/index.js +26 -4
- package/dist/api/strategies/index.js.map +1 -1
- package/dist/api/strategies/parallel.d.ts +15 -4
- package/dist/api/strategies/parallel.d.ts.map +1 -1
- package/dist/api/strategies/parallel.js +53 -16
- package/dist/api/strategies/parallel.js.map +1 -1
- package/dist/api/strategies/review-loop.d.ts +15 -1
- package/dist/api/strategies/review-loop.d.ts.map +1 -1
- package/dist/api/strategies/review-loop.js +36 -10
- package/dist/api/strategies/review-loop.js.map +1 -1
- package/dist/api/strategies/sequential.d.ts +11 -1
- package/dist/api/strategies/sequential.d.ts.map +1 -1
- package/dist/api/strategies/sequential.js +39 -8
- package/dist/api/strategies/sequential.js.map +1 -1
- package/dist/api/strategies/shared.d.ts +71 -7
- package/dist/api/strategies/shared.d.ts.map +1 -1
- package/dist/api/strategies/shared.js +89 -10
- package/dist/api/strategies/shared.js.map +1 -1
- package/dist/api/types.d.ts +54 -1
- package/dist/api/types.d.ts.map +1 -1
- package/dist/api/types.js.map +1 -1
- package/dist/memory/facade/Memory.d.ts.map +1 -1
- package/dist/memory/facade/Memory.js +8 -0
- package/dist/memory/facade/Memory.js.map +1 -1
- package/dist/memory/facade/types.d.ts +10 -0
- package/dist/memory/facade/types.d.ts.map +1 -1
- package/dist/memory/index.d.ts +6 -0
- package/dist/memory/index.d.ts.map +1 -1
- package/dist/memory/index.js +5 -0
- package/dist/memory/index.js.map +1 -1
- package/dist/memory/observation/MemoryObserver.d.ts +63 -1
- package/dist/memory/observation/MemoryObserver.d.ts.map +1 -1
- package/dist/memory/observation/MemoryObserver.js +115 -4
- package/dist/memory/observation/MemoryObserver.js.map +1 -1
- package/dist/memory/observation/ObservationCompressor.d.ts +88 -0
- package/dist/memory/observation/ObservationCompressor.d.ts.map +1 -0
- package/dist/memory/observation/ObservationCompressor.js +207 -0
- package/dist/memory/observation/ObservationCompressor.js.map +1 -0
- package/dist/memory/observation/ObservationReflector.d.ts +82 -0
- package/dist/memory/observation/ObservationReflector.d.ts.map +1 -0
- package/dist/memory/observation/ObservationReflector.js +212 -0
- package/dist/memory/observation/ObservationReflector.js.map +1 -0
- package/dist/memory/observation/temporal.d.ts +54 -0
- package/dist/memory/observation/temporal.d.ts.map +1 -0
- package/dist/memory/observation/temporal.js +115 -0
- package/dist/memory/observation/temporal.js.map +1 -0
- package/dist/orchestration/builders/VoiceNodeBuilder.d.ts +82 -25
- package/dist/orchestration/builders/VoiceNodeBuilder.d.ts.map +1 -1
- package/dist/orchestration/builders/VoiceNodeBuilder.js +86 -26
- package/dist/orchestration/builders/VoiceNodeBuilder.js.map +1 -1
- package/dist/orchestration/events/GraphEvent.d.ts +67 -5
- package/dist/orchestration/events/GraphEvent.d.ts.map +1 -1
- package/dist/orchestration/events/GraphEvent.js.map +1 -1
- package/dist/orchestration/runtime/VoiceNodeExecutor.d.ts +102 -25
- package/dist/orchestration/runtime/VoiceNodeExecutor.d.ts.map +1 -1
- package/dist/orchestration/runtime/VoiceNodeExecutor.js +133 -38
- package/dist/orchestration/runtime/VoiceNodeExecutor.js.map +1 -1
- package/dist/orchestration/runtime/VoiceTransportAdapter.d.ts +94 -32
- package/dist/orchestration/runtime/VoiceTransportAdapter.d.ts.map +1 -1
- package/dist/orchestration/runtime/VoiceTransportAdapter.js +82 -28
- package/dist/orchestration/runtime/VoiceTransportAdapter.js.map +1 -1
- package/dist/orchestration/runtime/VoiceTurnCollector.d.ts +73 -20
- package/dist/orchestration/runtime/VoiceTurnCollector.d.ts.map +1 -1
- package/dist/orchestration/runtime/VoiceTurnCollector.js +84 -23
- package/dist/orchestration/runtime/VoiceTurnCollector.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":"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":"AAAA;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GAqDG;AAIH;;;;;;;;;;GAUG;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,MAAM,SAAS,GAAG,GAAG,CAAC,WAAW,CAAuB,CAAC;QAEzD,iEAAiE;QACjE,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,MAAM,YAAY,GAAG,GAAG,CAAC,OAAO,CAAwC,CAAC;gBACzE,iEAAiE;gBACjE,MAAM,OAAO,GAAI,YAAY,EAAE,CAAC,SAAS,CAAwB,IAAI,EAAE,CAAC;gBACxE,MAAM,MAAM,GAAwB;oBAClC,IAAI,EAAE,OAAO;oBACb,SAAS;oBACT,OAAO;oBACP,QAAQ,EAAE,YAAmD;iBAC9D,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,mEAAmE;gBACnE,oEAAoE;gBACpE,sCAAsC;gBACtC,MAAM,KAAK,GAAG,KAAK,CAAC,OAAO,CAAuB,CAAC;gBACnD,IAAI,KAAK,KAAK,UAAU;oBAAE,OAAO,IAAI,CAAC;gBAEtC,MAAM,UAAU,GAAG,KAAK,CAAC,SAAS,CAAuB,CAAC;gBAC1D,IAAI,CAAC,UAAU;oBAAE,OAAO,IAAI,CAAC;gBAE7B,MAAM,cAAc,GAAG,OAAO,GAAG,CAAC,gBAAgB,CAAC,KAAK,QAAQ;oBAC9D,CAAC,CAAE,GAAG,CAAC,gBAAgB,CAAY;oBACnC,CAAC,CAAC,SAAS,CAAC;gBAEd,MAAM,MAAM,GAAwB;oBAClC,IAAI,EAAE,OAAO;oBACb,OAAO,EAAE,MAAM,CAAC,IAAI,CAAC,UAAU,EAAE,QAAQ,CAAC;oBAC1C,SAAS;oBACT,GAAG,CAAC,cAAc,KAAK,SAAS,CAAC,CAAC,CAAC,EAAE,cAAc,EAAE,CAAC,CAAC,CAAC,EAAE,CAAC;iBAC5D,CAAC;gBACF,OAAO,MAAM,CAAC;YAChB,CAAC;YAED,KAAK,MAAM,CAAC,CAAC,CAAC;gBACZ,MAAM,IAAI,GAAG,GAAG,CAAC,MAAM,CAAwC,CAAC;gBAChE,IAAI,CAAC,IAAI;oBAAE,OAAO,IAAI,CAAC;gBAEvB,MAAM,KAAK,GAAG,IAAI,CAAC,OAAO,CAAuB,CAAC;gBAClD,IAAI,CAAC,KAAK;oBAAE,OAAO,IAAI,CAAC;gBAExB,yDAAyD;gBACzD,MAAM,QAAQ,GAAG,OAAO,IAAI,CAAC,UAAU,CAAC,KAAK,QAAQ;oBACnD,CAAC,CAAE,IAAI,CAAC,UAAU,CAAY;oBAC9B,CAAC,CAAC,SAAS,CAAC;gBAEd,MAAM,MAAM,GAAwB;oBAClC,IAAI,EAAE,MAAM;oBACZ,KAAK;oBACL,SAAS;oBACT,GAAG,CAAC,QAAQ,KAAK,SAAS,CAAC,CAAC,CAAC,EAAE,UAAU,EAAE,QAAQ,EAAE,CAAC,CAAC,CAAC,EAAE,CAAC;iBAC5D,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,KAAK,MAAM,CAAC,CAAC,CAAC;gBACZ,MAAM,IAAI,GAAG,GAAG,CAAC,MAAM,CAAwC,CAAC;gBAChE,IAAI,CAAC,IAAI;oBAAE,OAAO,IAAI,CAAC;gBAEvB,MAAM,IAAI,GAAG,IAAI,CAAC,MAAM,CAAuB,CAAC;gBAChD,IAAI,CAAC,IAAI;oBAAE,OAAO,IAAI,CAAC;gBAEvB,MAAM,MAAM,GAAwB,EAAE,IAAI,EAAE,MAAM,EAAE,IAAI,EAAE,SAAS,EAAE,CAAC;gBACtE,OAAO,MAAM,CAAC;YAChB,CAAC;YAED;gBACE,qEAAqE;gBACrE,0EAA0E;gBAC1E,OAAO,IAAI,CAAC;QAChB,CAAC;IACH,CAAC;IAED;;;;;;;;;;OAUG;IACH,cAAc,CAAC,KAAa,EAAE,SAAiB;QAC7C,OAAO,IAAI,CAAC,SAAS,CAAC;YACpB,KAAK,EAAE,OAAO;YACd,SAAS;YACT,KAAK,EAAE,EAAE,OAAO,EAAE,KAAK,CAAC,QAAQ,CAAC,QAAQ,CAAC,EAAE;SAC7C,CAAC,CAAC;IACL,CAAC;IAED;;;;;;;;;;OAUG;IACH,eAAe,CAAC,UAAkB;QAChC,OAAO,IAAI,CAAC,SAAS,CAAC;YACpB,KAAK,EAAE,WAAW;YAClB,QAAQ,EAAE,MAAM;YAChB,OAAO,EAAE,OAAO;SACjB,CAAC,CAAC;IACL,CAAC;CACF"}
|
|
@@ -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"}
|
|
@@ -2,7 +2,63 @@
|
|
|
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
|
*/
|
|
@@ -25,10 +81,15 @@ import { createHmac, randomUUID } from 'node:crypto';
|
|
|
25
81
|
* ```
|
|
26
82
|
*/
|
|
27
83
|
export class PlivoVoiceProvider {
|
|
84
|
+
/**
|
|
85
|
+
* @param config - Plivo credentials and optional overrides.
|
|
86
|
+
*/
|
|
28
87
|
constructor(config) {
|
|
88
|
+
/** Provider identifier, always `'plivo'`. */
|
|
29
89
|
this.name = 'plivo';
|
|
30
90
|
this.config = config;
|
|
31
91
|
this.baseUrl = 'https://api.plivo.com/v1';
|
|
92
|
+
// Plivo uses HTTP Basic auth with authId:authToken (similar to Twilio).
|
|
32
93
|
this.authHeader =
|
|
33
94
|
'Basic ' + Buffer.from(`${config.authId}:${config.authToken}`).toString('base64');
|
|
34
95
|
this.fetch = config.fetchImpl ?? globalThis.fetch;
|
|
@@ -37,9 +98,17 @@ export class PlivoVoiceProvider {
|
|
|
37
98
|
/**
|
|
38
99
|
* Verify an incoming Plivo webhook request using HMAC-SHA256 (v3 scheme).
|
|
39
100
|
*
|
|
40
|
-
*
|
|
41
|
-
*
|
|
42
|
-
* the `
|
|
101
|
+
* ## Algorithm (step by step)
|
|
102
|
+
*
|
|
103
|
+
* 1. Extract the `X-Plivo-Signature-V3-Nonce` and `X-Plivo-Signature-V3` headers.
|
|
104
|
+
* 2. Build the signed data string: `{fullRequestURL}{nonce}`.
|
|
105
|
+
* Note: the POST body is NOT included in the signed data (unlike Twilio).
|
|
106
|
+
* 3. Compute `HMAC-SHA256(authToken, signedData)`.
|
|
107
|
+
* 4. Base64-encode the digest.
|
|
108
|
+
* 5. Compare with the `X-Plivo-Signature-V3` header.
|
|
109
|
+
*
|
|
110
|
+
* @param ctx - Raw webhook request context.
|
|
111
|
+
* @returns Verification result with `valid: true` if the signature matches.
|
|
43
112
|
*/
|
|
44
113
|
verifyWebhook(ctx) {
|
|
45
114
|
const nonce = ctx.headers['x-plivo-signature-v3-nonce'];
|
|
@@ -47,6 +116,8 @@ export class PlivoVoiceProvider {
|
|
|
47
116
|
if (Array.isArray(nonce) || Array.isArray(signature)) {
|
|
48
117
|
return { valid: false, error: 'Duplicate Plivo signature headers' };
|
|
49
118
|
}
|
|
119
|
+
// Use empty string as nonce fallback when header is missing --
|
|
120
|
+
// the HMAC will still compute but won't match the expected signature.
|
|
50
121
|
const nonceStr = nonce ?? '';
|
|
51
122
|
const data = ctx.url + nonceStr;
|
|
52
123
|
const expected = createHmac('sha256', this.config.authToken)
|
|
@@ -58,8 +129,17 @@ export class PlivoVoiceProvider {
|
|
|
58
129
|
/**
|
|
59
130
|
* Parse a Plivo webhook body into normalized {@link NormalizedCallEvent}s.
|
|
60
131
|
*
|
|
61
|
-
*
|
|
62
|
-
*
|
|
132
|
+
* Plivo sends most webhooks with URL-encoded bodies, but some callbacks
|
|
133
|
+
* may arrive as JSON. This parser handles both formats by inspecting
|
|
134
|
+
* whether the body starts with `{` (JSON) or not (form-encoded).
|
|
135
|
+
*
|
|
136
|
+
* Plivo uses two naming conventions for the same fields:
|
|
137
|
+
* - PascalCase (`CallUUID`, `CallStatus`, `Digits`) in URL callbacks.
|
|
138
|
+
* - snake_case (`call_uuid`, `call_status`) in some API responses.
|
|
139
|
+
* Both are checked for maximum compatibility.
|
|
140
|
+
*
|
|
141
|
+
* @param ctx - Raw webhook request context.
|
|
142
|
+
* @returns Parsed result containing zero or more normalized events.
|
|
63
143
|
*/
|
|
64
144
|
parseWebhookEvent(ctx) {
|
|
65
145
|
const body = ctx.body.toString();
|
|
@@ -77,16 +157,19 @@ export class PlivoVoiceProvider {
|
|
|
77
157
|
catch {
|
|
78
158
|
params = new URLSearchParams(body);
|
|
79
159
|
}
|
|
160
|
+
// Support both PascalCase and snake_case field naming conventions.
|
|
80
161
|
const callUuid = params.get('CallUUID') ?? params.get('call_uuid') ?? '';
|
|
81
162
|
const callStatus = params.get('CallStatus') ?? params.get('call_status') ?? '';
|
|
82
163
|
const digits = params.get('Digits');
|
|
83
164
|
const timestamp = Date.now();
|
|
84
165
|
const events = [];
|
|
166
|
+
/** Helper: shared base fields with a unique event ID for idempotency. */
|
|
85
167
|
const base = () => ({
|
|
86
168
|
eventId: randomUUID(),
|
|
87
169
|
providerCallId: callUuid,
|
|
88
170
|
timestamp,
|
|
89
171
|
});
|
|
172
|
+
// Map Plivo CallStatus values to normalized event kinds.
|
|
90
173
|
switch (callStatus) {
|
|
91
174
|
case 'ringing':
|
|
92
175
|
events.push({ ...base(), kind: 'call-ringing' });
|
|
@@ -107,9 +190,10 @@ export class PlivoVoiceProvider {
|
|
|
107
190
|
events.push({ ...base(), kind: 'call-failed' });
|
|
108
191
|
break;
|
|
109
192
|
default:
|
|
110
|
-
// initiated / queued / etc.
|
|
193
|
+
// initiated / queued / etc. -- no normalized event emitted.
|
|
111
194
|
break;
|
|
112
195
|
}
|
|
196
|
+
// DTMF digit input (from <GetDigits> XML element callback).
|
|
113
197
|
if (digits != null && digits !== '') {
|
|
114
198
|
events.push({ ...base(), kind: 'call-dtmf', digit: digits });
|
|
115
199
|
}
|
|
@@ -119,8 +203,12 @@ export class PlivoVoiceProvider {
|
|
|
119
203
|
/**
|
|
120
204
|
* Initiate an outbound call via the Plivo Call API.
|
|
121
205
|
*
|
|
122
|
-
* POSTs a JSON body to `/Account/{authId}/Call/` with the caller, callee,
|
|
206
|
+
* POSTs a JSON body to `/v1/Account/{authId}/Call/` with the caller, callee,
|
|
123
207
|
* and answer URL. Returns the `request_uuid` as the provider call ID.
|
|
208
|
+
*
|
|
209
|
+
* @param input - Call initiation parameters (from/to numbers, webhook URL).
|
|
210
|
+
* @returns Result containing the Plivo `request_uuid` on success.
|
|
211
|
+
* @throws Never throws; returns `{ success: false, error: '...' }` on failure.
|
|
124
212
|
*/
|
|
125
213
|
async initiateCall(input) {
|
|
126
214
|
const url = `${this.baseUrl}/Account/${this.config.authId}/Call/`;
|
|
@@ -146,6 +234,12 @@ export class PlivoVoiceProvider {
|
|
|
146
234
|
}
|
|
147
235
|
/**
|
|
148
236
|
* Hang up an active call using the Plivo Call DELETE endpoint.
|
|
237
|
+
*
|
|
238
|
+
* Plivo uses HTTP `DELETE` to terminate a call (unlike Twilio's POST with
|
|
239
|
+
* `Status=completed` or Telnyx's POST to `/actions/hangup`). This is a
|
|
240
|
+
* RESTful convention where deleting the call resource ends the call.
|
|
241
|
+
*
|
|
242
|
+
* @param input - Contains the Plivo `call_uuid` to hang up.
|
|
149
243
|
*/
|
|
150
244
|
async hangupCall(input) {
|
|
151
245
|
const url = `${this.baseUrl}/Account/${this.config.authId}/Call/${input.providerCallId}/`;
|
|
@@ -159,7 +253,10 @@ export class PlivoVoiceProvider {
|
|
|
159
253
|
/**
|
|
160
254
|
* Speak text into a live call using the Plivo Speak API.
|
|
161
255
|
*
|
|
162
|
-
*
|
|
256
|
+
* POSTs a JSON body to `/v1/Account/{authId}/Call/{callUuid}/Speak/`
|
|
257
|
+
* with the text, voice (default `'WOMAN'`), and language (default `'en-US'`).
|
|
258
|
+
*
|
|
259
|
+
* @param input - TTS parameters (text, optional voice, call ID).
|
|
163
260
|
*/
|
|
164
261
|
async playTts(input) {
|
|
165
262
|
const url = `${this.baseUrl}/Account/${this.config.authId}/Call/${input.providerCallId}/Speak/`;
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"plivo.js","sourceRoot":"","sources":["../../../src/voice/providers/plivo.ts"],"names":[],"mappings":"AAAA
|
|
1
|
+
{"version":3,"file":"plivo.js","sourceRoot":"","sources":["../../../src/voice/providers/plivo.ts"],"names":[],"mappings":"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"}
|
|
@@ -2,14 +2,64 @@
|
|
|
2
2
|
* @fileoverview Telnyx telephony provider for AgentOS voice calls.
|
|
3
3
|
*
|
|
4
4
|
* Implements {@link IVoiceCallProvider} using the Telnyx Call Control v2 API.
|
|
5
|
-
*
|
|
6
|
-
*
|
|
5
|
+
*
|
|
6
|
+
* ## REST API contract
|
|
7
|
+
*
|
|
8
|
+
* | Operation | Method | Endpoint | Body format |
|
|
9
|
+
* |----------------|--------|------------------------------------------|-------------|
|
|
10
|
+
* | Initiate call | POST | `/v2/calls` | JSON |
|
|
11
|
+
* | Hangup call | POST | `/v2/calls/{id}/actions/hangup` | JSON |
|
|
12
|
+
* | Play TTS | POST | `/v2/calls/{id}/actions/speak` | JSON |
|
|
13
|
+
* | Start stream | POST | `/v2/calls/{id}/actions/streaming_start` | JSON |
|
|
14
|
+
*
|
|
15
|
+
* All requests use Bearer token authentication: `Authorization: Bearer {apiKey}`.
|
|
16
|
+
* Request bodies are JSON (unlike Twilio's form-encoded convention).
|
|
17
|
+
*
|
|
18
|
+
* ## Streaming after `call.answered`
|
|
19
|
+
*
|
|
20
|
+
* Telnyx requires a two-step flow for media streaming:
|
|
21
|
+
* 1. Initiate the call via `POST /v2/calls` with a `webhook_url`.
|
|
22
|
+
* 2. When the `call.answered` webhook fires, issue a separate
|
|
23
|
+
* `POST /v2/calls/{id}/actions/streaming_start` request with the
|
|
24
|
+
* WebSocket URL. This is handled by the CallManager, not by this provider.
|
|
25
|
+
*
|
|
26
|
+
* ## Webhook verification: Ed25519
|
|
27
|
+
*
|
|
28
|
+
* Telnyx signs webhooks using Ed25519 public key cryptography:
|
|
29
|
+
*
|
|
30
|
+
* 1. Telnyx generates a signed payload: `{timestamp}|{rawBody}`.
|
|
31
|
+
* 2. The signature is computed with the account's Ed25519 private key.
|
|
32
|
+
* 3. The public key is provided as a base64-encoded DER SPKI blob.
|
|
33
|
+
* 4. Headers: `X-Telnyx-Timestamp` (the timestamp) and
|
|
34
|
+
* `X-Telnyx-Signature-Ed25519` (base64-encoded Ed25519 signature).
|
|
35
|
+
* 5. Verification: decode the signature from base64, construct the payload
|
|
36
|
+
* string `{timestamp}|{body}`, and verify using `crypto.verify()` with
|
|
37
|
+
* the SPKI public key.
|
|
38
|
+
*
|
|
39
|
+
* When no public key is configured, verification is skipped (returns
|
|
40
|
+
* `valid: true`) to support development environments.
|
|
41
|
+
*
|
|
42
|
+
* ## Event mapping table (hangup_cause)
|
|
43
|
+
*
|
|
44
|
+
* | Telnyx `event_type` | `hangup_cause` | Normalised `kind` |
|
|
45
|
+
* |----------------------------------|---------------------------|----------------------|
|
|
46
|
+
* | `call.initiated` | -- | `call-ringing` |
|
|
47
|
+
* | `call.answered` | -- | `call-answered` |
|
|
48
|
+
* | `call.hangup` | `normal_clearing` | `call-hangup-user` |
|
|
49
|
+
* | `call.hangup` | `user_busy` | `call-hangup-user` |
|
|
50
|
+
* | `call.hangup` | `originator_cancel` | `call-hangup-user` |
|
|
51
|
+
* | `call.hangup` | (anything else) | `call-completed` |
|
|
52
|
+
* | `call.dtmf.received` | -- | `call-dtmf` |
|
|
53
|
+
* | `call.machine.detection.ended` | result=`machine` | `call-voicemail` |
|
|
54
|
+
* | `call.machine.detection.ended` | result=`human` | (no event) |
|
|
7
55
|
*
|
|
8
56
|
* @module @framers/agentos/voice/providers/telnyx
|
|
9
57
|
*/
|
|
10
58
|
import type { IVoiceCallProvider, InitiateCallInput, InitiateCallResult, HangupCallInput, PlayTtsInput } from '../IVoiceCallProvider.js';
|
|
11
59
|
import type { WebhookContext, WebhookVerificationResult, WebhookParseResult } from '../types.js';
|
|
12
|
-
/**
|
|
60
|
+
/**
|
|
61
|
+
* Configuration for {@link TelnyxVoiceProvider}.
|
|
62
|
+
*/
|
|
13
63
|
export interface TelnyxVoiceProviderConfig {
|
|
14
64
|
/** Telnyx API key (starts with "KEY"). */
|
|
15
65
|
apiKey: string;
|
|
@@ -17,11 +67,13 @@ export interface TelnyxVoiceProviderConfig {
|
|
|
17
67
|
connectionId: string;
|
|
18
68
|
/**
|
|
19
69
|
* Base64-encoded DER-encoded SPKI Ed25519 public key for webhook verification.
|
|
20
|
-
*
|
|
70
|
+
*
|
|
71
|
+
* When omitted, webhook verification is skipped (always returns `valid: true`).
|
|
72
|
+
* This is acceptable for development but should always be set in production.
|
|
21
73
|
*/
|
|
22
74
|
publicKey?: string;
|
|
23
75
|
/**
|
|
24
|
-
* Optional fetch override
|
|
76
|
+
* Optional fetch implementation override -- inject a mock in tests.
|
|
25
77
|
* Defaults to the global `fetch`.
|
|
26
78
|
*/
|
|
27
79
|
fetchImpl?: typeof fetch;
|
|
@@ -43,50 +95,88 @@ export interface TelnyxVoiceProviderConfig {
|
|
|
43
95
|
* ```
|
|
44
96
|
*/
|
|
45
97
|
export declare class TelnyxVoiceProvider implements IVoiceCallProvider {
|
|
98
|
+
/** Provider identifier, always `'telnyx'`. */
|
|
46
99
|
readonly name: "telnyx";
|
|
100
|
+
/** Immutable configuration snapshot. */
|
|
47
101
|
private readonly config;
|
|
102
|
+
/** Base URL for the Telnyx v2 API. */
|
|
48
103
|
private readonly baseUrl;
|
|
104
|
+
/** Pre-computed `Authorization: Bearer ...` header value. */
|
|
49
105
|
private readonly authHeader;
|
|
106
|
+
/** HTTP fetch implementation (injectable for testing). */
|
|
50
107
|
private readonly fetch;
|
|
108
|
+
/**
|
|
109
|
+
* @param config - Telnyx credentials and optional overrides.
|
|
110
|
+
*/
|
|
51
111
|
constructor(config: TelnyxVoiceProviderConfig);
|
|
52
112
|
/**
|
|
53
113
|
* Verify an incoming Telnyx webhook using Ed25519 signature verification.
|
|
54
114
|
*
|
|
55
|
-
*
|
|
56
|
-
*
|
|
115
|
+
* ## Algorithm (step by step)
|
|
116
|
+
*
|
|
117
|
+
* 1. If no public key is configured, skip verification (return `valid: true`).
|
|
118
|
+
* This supports development environments without cryptographic setup.
|
|
119
|
+
* 2. Extract `X-Telnyx-Timestamp` and `X-Telnyx-Signature-Ed25519` headers.
|
|
120
|
+
* 3. Decode the signature from base64 into a raw byte Buffer.
|
|
121
|
+
* 4. Construct the signed payload: `"{timestamp}|{rawBody}"`.
|
|
122
|
+
* 5. Decode the SPKI public key from base64.
|
|
123
|
+
* 6. Call `crypto.verify(null, payload, { key, format: 'der', type: 'spki' }, signature)`.
|
|
124
|
+
* 7. Return the verification result.
|
|
57
125
|
*
|
|
58
|
-
*
|
|
59
|
-
*
|
|
60
|
-
*
|
|
126
|
+
* @param ctx - Raw webhook request context.
|
|
127
|
+
* @returns Verification result. Returns `{ valid: true }` when no public key
|
|
128
|
+
* is configured (development mode).
|
|
61
129
|
*/
|
|
62
130
|
verifyWebhook(ctx: WebhookContext): WebhookVerificationResult;
|
|
63
131
|
/**
|
|
64
132
|
* Parse a Telnyx webhook JSON body into normalized {@link NormalizedCallEvent}s.
|
|
65
133
|
*
|
|
66
|
-
*
|
|
67
|
-
*
|
|
68
|
-
*
|
|
69
|
-
*
|
|
70
|
-
*
|
|
71
|
-
*
|
|
134
|
+
* Telnyx sends all webhook payloads as JSON with a `data.event_type`
|
|
135
|
+
* discriminant field. The `data.payload` object contains call-specific
|
|
136
|
+
* fields like `call_control_id`, `hangup_cause`, `digit`, and `result`.
|
|
137
|
+
*
|
|
138
|
+
* ## Hangup cause mapping
|
|
139
|
+
*
|
|
140
|
+
* Telnyx's `call.hangup` event includes a `hangup_cause` field that must
|
|
141
|
+
* be inspected to determine whether the user or the system terminated
|
|
142
|
+
* the call:
|
|
143
|
+
* - `normal_clearing` / `user_busy` / `originator_cancel` -> `call-hangup-user`
|
|
144
|
+
* - All other causes (e.g., `call_rejected`, `unallocated_number`) -> `call-completed`
|
|
145
|
+
*
|
|
146
|
+
* @param ctx - Raw webhook request context.
|
|
147
|
+
* @returns Parsed result containing zero or more normalized events.
|
|
72
148
|
*/
|
|
73
149
|
parseWebhookEvent(ctx: WebhookContext): WebhookParseResult;
|
|
74
150
|
/**
|
|
75
151
|
* Initiate an outbound call via the Telnyx Call Control v2 API.
|
|
76
152
|
*
|
|
77
|
-
* POSTs to `/calls` with a JSON body
|
|
78
|
-
*
|
|
79
|
-
*
|
|
153
|
+
* POSTs to `/v2/calls` with a JSON body containing the `connection_id`,
|
|
154
|
+
* phone numbers, and webhook URL. The `mediaStreamUrl` (if provided) is
|
|
155
|
+
* stored internally for use after the call is answered -- it is NOT sent
|
|
156
|
+
* in the initial call creation request because Telnyx requires
|
|
157
|
+
* `streaming_start` to be issued as a separate action after `call.answered`.
|
|
158
|
+
*
|
|
159
|
+
* @param input - Call initiation parameters (from/to numbers, webhook URL).
|
|
160
|
+
* @returns Result containing the Telnyx `call_control_id` on success.
|
|
161
|
+
* @throws Never throws; returns `{ success: false, error: '...' }` on failure.
|
|
80
162
|
*/
|
|
81
163
|
initiateCall(input: InitiateCallInput): Promise<InitiateCallResult>;
|
|
82
164
|
/**
|
|
83
165
|
* Hang up an active call via the Telnyx Call Control hangup action.
|
|
166
|
+
*
|
|
167
|
+
* POSTs an empty JSON body to `/v2/calls/{call_control_id}/actions/hangup`.
|
|
168
|
+
* Telnyx will terminate the call and fire a `call.hangup` webhook.
|
|
169
|
+
*
|
|
170
|
+
* @param input - Contains the Telnyx `call_control_id` to hang up.
|
|
84
171
|
*/
|
|
85
172
|
hangupCall(input: HangupCallInput): Promise<void>;
|
|
86
173
|
/**
|
|
87
174
|
* Speak text into a live call using Telnyx's text-to-speech speak action.
|
|
88
175
|
*
|
|
89
|
-
*
|
|
176
|
+
* POSTs a JSON body to `/v2/calls/{id}/actions/speak` with the text
|
|
177
|
+
* `payload`, `voice` (default `'female'`), and `language` (default `'en-US'`).
|
|
178
|
+
*
|
|
179
|
+
* @param input - TTS parameters (text, optional voice, call ID).
|
|
90
180
|
*/
|
|
91
181
|
playTts(input: PlayTtsInput): Promise<void>;
|
|
92
182
|
}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"telnyx.d.ts","sourceRoot":"","sources":["../../../src/voice/providers/telnyx.ts"],"names":[],"mappings":"AAAA
|
|
1
|
+
{"version":3,"file":"telnyx.d.ts","sourceRoot":"","sources":["../../../src/voice/providers/telnyx.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GAwDG;AAKH,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,yBAAyB;IACxC,0CAA0C;IAC1C,MAAM,EAAE,MAAM,CAAC;IACf,yDAAyD;IACzD,YAAY,EAAE,MAAM,CAAC;IACrB;;;;;OAKG;IACH,SAAS,CAAC,EAAE,MAAM,CAAC;IACnB;;;OAGG;IACH,SAAS,CAAC,EAAE,OAAO,KAAK,CAAC;CAC1B;AAiCD;;;;;;;;;;;;;;;GAeG;AACH,qBAAa,mBAAoB,YAAW,kBAAkB;IAC5D,8CAA8C;IAC9C,QAAQ,CAAC,IAAI,EAAG,QAAQ,CAAU;IAElC,wCAAwC;IACxC,OAAO,CAAC,QAAQ,CAAC,MAAM,CAA4B;IAEnD,sCAAsC;IACtC,OAAO,CAAC,QAAQ,CAAC,OAAO,CAAS;IAEjC,6DAA6D;IAC7D,OAAO,CAAC,QAAQ,CAAC,UAAU,CAAS;IAEpC,0DAA0D;IAC1D,OAAO,CAAC,QAAQ,CAAC,KAAK,CAAe;IAErC;;OAEG;gBACS,MAAM,EAAE,yBAAyB;IAS7C;;;;;;;;;;;;;;;;;OAiBG;IACH,aAAa,CAAC,GAAG,EAAE,cAAc,GAAG,yBAAyB;IAoC7D;;;;;;;;;;;;;;;;;OAiBG;IACH,iBAAiB,CAAC,GAAG,EAAE,cAAc,GAAG,kBAAkB;IA6D1D;;;;;;;;;;;;OAYG;IACG,YAAY,CAAC,KAAK,EAAE,iBAAiB,GAAG,OAAO,CAAC,kBAAkB,CAAC;IA0BzE;;;;;;;OAOG;IACG,UAAU,CAAC,KAAK,EAAE,eAAe,GAAG,OAAO,CAAC,IAAI,CAAC;IAavD;;;;;;;OAOG;IACG,OAAO,CAAC,KAAK,EAAE,YAAY,GAAG,OAAO,CAAC,IAAI,CAAC;CAgBlD"}
|