getpatter 0.6.3 → 0.6.4
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 +5 -4
- package/dist/{carrier-config-3WDQXP5J.mjs → carrier-config-7YGNRBPO.mjs} +17 -11
- package/dist/{chunk-R2T4JABZ.mjs → chunk-3VVATR6A.mjs} +8 -6
- package/dist/{chunk-Z6W5XFWS.mjs → chunk-7IIV3BY4.mjs} +981 -196
- package/dist/{chunk-CL2U3YET.mjs → chunk-BO227NTF.mjs} +271 -54
- package/dist/cli.js +63 -20
- package/dist/dashboard/ui.html +10 -10
- package/dist/index.d.mts +867 -187
- package/dist/index.d.ts +867 -187
- package/dist/index.js +1785 -517
- package/dist/index.mjs +501 -250
- package/dist/{openai-realtime-2-CNFARP25.mjs → openai-realtime-2-L5EKAAUH.mjs} +1 -1
- package/dist/{silero-vad-LNDFGIY7.mjs → silero-vad-RGF5HCIR.mjs} +1 -1
- package/dist/{test-mode-MDBQ4ECE.mjs → test-mode-4QLLWYVV.mjs} +2 -2
- package/package.json +2 -1
- package/src/dashboard/ui.html +10 -10
package/README.md
CHANGED
|
@@ -74,6 +74,7 @@ Every provider reads its credentials from the environment by default. Pass `apiK
|
|
|
74
74
|
|---|---|
|
|
75
75
|
| `TWILIO_ACCOUNT_SID`, `TWILIO_AUTH_TOKEN` | `new Twilio()` carrier |
|
|
76
76
|
| `TELNYX_API_KEY`, `TELNYX_CONNECTION_ID`, `TELNYX_PUBLIC_KEY` (optional) | `new Telnyx()` carrier |
|
|
77
|
+
| `PLIVO_AUTH_ID`, `PLIVO_AUTH_TOKEN` | `new Plivo()` carrier — Auth Token doubles as the V3 webhook signature key |
|
|
77
78
|
| `OPENAI_API_KEY` | `OpenAIRealtime`, `WhisperSTT`, `OpenAITTS` |
|
|
78
79
|
| `ELEVENLABS_API_KEY`, `ELEVENLABS_AGENT_ID` | `ElevenLabsConvAI`, `ElevenLabsTTS` |
|
|
79
80
|
| `DEEPGRAM_API_KEY` | `DeepgramSTT` |
|
|
@@ -92,7 +93,7 @@ cp .env.example .env
|
|
|
92
93
|
# Edit .env with your API keys
|
|
93
94
|
```
|
|
94
95
|
|
|
95
|
-
> **
|
|
96
|
+
> **Other carriers:** **Telnyx** and **Plivo** are both fully supported alternatives to Twilio. All three carriers receive equal support for inbound DTMF, transfer, AMD, status callbacks, recording, voicemail drop, and metrics. **Plivo** additionally supports native DTMF *send* over the media WebSocket — a capability Twilio Media Streams lacks. Plivo's Auth Token doubles as the V3 webhook signature key (no separate public key, unlike Telnyx Ed25519).
|
|
96
97
|
|
|
97
98
|
## Voice Modes
|
|
98
99
|
|
|
@@ -108,7 +109,7 @@ cp .env.example .env
|
|
|
108
109
|
|
|
109
110
|
```typescript
|
|
110
111
|
new Patter({
|
|
111
|
-
carrier: Twilio | Telnyx;
|
|
112
|
+
carrier: Twilio | Telnyx | Plivo;
|
|
112
113
|
phoneNumber: string;
|
|
113
114
|
webhookUrl?: string; // Public hostname. Mutually exclusive with tunnel.
|
|
114
115
|
tunnel?: CloudflareTunnel | StaticTunnel | boolean; // `true` is shorthand for new CloudflareTunnel().
|
|
@@ -117,7 +118,7 @@ new Patter({
|
|
|
117
118
|
|
|
118
119
|
| Parameter | Type | Description |
|
|
119
120
|
|---|---|---|
|
|
120
|
-
| `carrier` | `Twilio` / `Telnyx` | Carrier instance. Reads env vars by default. |
|
|
121
|
+
| `carrier` | `Twilio` / `Telnyx` / `Plivo` | Carrier instance. Reads env vars by default. |
|
|
121
122
|
| `phoneNumber` | `string` | Your phone number in E.164 format. |
|
|
122
123
|
| `webhookUrl` | `string` | Public hostname your local server is reachable on. |
|
|
123
124
|
| `tunnel` | `CloudflareTunnel \| StaticTunnel \| boolean` | `new CloudflareTunnel()`, `new StaticTunnel({ hostname: ... })`, or `true` (shorthand for `new CloudflareTunnel()`). |
|
|
@@ -179,7 +180,7 @@ await phone.call({
|
|
|
179
180
|
```typescript
|
|
180
181
|
import {
|
|
181
182
|
// Carriers
|
|
182
|
-
Twilio, Telnyx,
|
|
183
|
+
Twilio, Telnyx, Plivo,
|
|
183
184
|
// Engines
|
|
184
185
|
OpenAIRealtime, ElevenLabsConvAI,
|
|
185
186
|
// STT
|
|
@@ -7,6 +7,9 @@ import {
|
|
|
7
7
|
|
|
8
8
|
// src/carrier-config.ts
|
|
9
9
|
init_esm_shims();
|
|
10
|
+
function redactPhone(n) {
|
|
11
|
+
return n.slice(0, 3) + "***" + n.slice(-4);
|
|
12
|
+
}
|
|
10
13
|
var TWILIO_API_BASE = "https://api.twilio.com/2010-04-01";
|
|
11
14
|
var TELNYX_API_BASE = "https://api.telnyx.com/v2";
|
|
12
15
|
var PLIVO_API_BASE = "https://api.plivo.com/v1";
|
|
@@ -25,7 +28,7 @@ async function configureTwilioNumber(accountSid, authToken, phoneNumber, voiceUr
|
|
|
25
28
|
const body = await listResp.json();
|
|
26
29
|
const match = body.incoming_phone_numbers?.[0];
|
|
27
30
|
if (!match) {
|
|
28
|
-
throw new Error(`Twilio number ${phoneNumber} not found on account ${accountSid}`);
|
|
31
|
+
throw new Error(`Twilio number ${redactPhone(phoneNumber)} not found on account ${accountSid}`);
|
|
29
32
|
}
|
|
30
33
|
const updateUrl = `${TWILIO_API_BASE}/Accounts/${accountSid}/IncomingPhoneNumbers/${match.sid}.json`;
|
|
31
34
|
const form = new URLSearchParams({ VoiceUrl: voiceUrl, VoiceMethod: "POST" });
|
|
@@ -44,17 +47,20 @@ async function configureTwilioNumber(accountSid, authToken, phoneNumber, voiceUr
|
|
|
44
47
|
}
|
|
45
48
|
}
|
|
46
49
|
async function configureTelnyxNumber(apiKey, connectionId, phoneNumber) {
|
|
47
|
-
const resp = await fetch(
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
50
|
+
const resp = await fetch(
|
|
51
|
+
`${TELNYX_API_BASE}/phone_numbers/${encodeURIComponent(phoneNumber)}/voice`,
|
|
52
|
+
{
|
|
53
|
+
method: "PATCH",
|
|
54
|
+
headers: {
|
|
55
|
+
Authorization: `Bearer ${apiKey}`,
|
|
56
|
+
"Content-Type": "application/json"
|
|
57
|
+
},
|
|
58
|
+
body: JSON.stringify({ connection_id: connectionId, tech_prefix_enabled: false })
|
|
59
|
+
}
|
|
60
|
+
);
|
|
55
61
|
if (!resp.ok) {
|
|
56
62
|
throw new Error(
|
|
57
|
-
`Telnyx PATCH /phone_numbers/${phoneNumber} failed: ${resp.status} ${await resp.text()}`
|
|
63
|
+
`Telnyx PATCH /phone_numbers/${redactPhone(phoneNumber)}/voice failed: ${resp.status} ${await resp.text()}`
|
|
58
64
|
);
|
|
59
65
|
}
|
|
60
66
|
}
|
|
@@ -104,7 +110,7 @@ async function autoConfigureCarrier(params) {
|
|
|
104
110
|
if (provider === "telnyx" && params.telnyxKey && params.telnyxConnectionId) {
|
|
105
111
|
try {
|
|
106
112
|
await configureTelnyxNumber(params.telnyxKey, params.telnyxConnectionId, params.phoneNumber);
|
|
107
|
-
log.info("Telnyx number
|
|
113
|
+
log.info("Telnyx number ***%s associated with connection %s", params.phoneNumber.slice(-4), params.telnyxConnectionId);
|
|
108
114
|
} catch (err) {
|
|
109
115
|
log.warn("Could not auto-configure Telnyx number: %s", err instanceof Error ? err.message : String(err));
|
|
110
116
|
}
|
|
@@ -193,6 +193,8 @@ var SileroVAD = class _SileroVAD {
|
|
|
193
193
|
speechThresholdDuration = 0;
|
|
194
194
|
silenceThresholdDuration = 0;
|
|
195
195
|
closed = false;
|
|
196
|
+
/** Transitions produced in the current processFrame call but not yet returned. */
|
|
197
|
+
eventQueue = [];
|
|
196
198
|
/**
|
|
197
199
|
* Load the Silero VAD model.
|
|
198
200
|
* Throws if `onnxruntime-node` is not installed.
|
|
@@ -318,22 +320,21 @@ var SileroVAD = class _SileroVAD {
|
|
|
318
320
|
);
|
|
319
321
|
}
|
|
320
322
|
if (pcmChunk.length === 0) {
|
|
321
|
-
return null;
|
|
323
|
+
return this.eventQueue.shift() ?? null;
|
|
322
324
|
}
|
|
323
325
|
const numSamples = Math.floor(pcmChunk.length / 2);
|
|
324
326
|
if (numSamples === 0) {
|
|
325
|
-
return null;
|
|
327
|
+
return this.eventQueue.shift() ?? null;
|
|
326
328
|
}
|
|
327
329
|
const samples = new Float32Array(numSamples);
|
|
328
330
|
for (let i = 0; i < numSamples; i++) {
|
|
329
|
-
samples[i] = pcmChunk.readInt16LE(i * 2) /
|
|
331
|
+
samples[i] = pcmChunk.readInt16LE(i * 2) / 32768;
|
|
330
332
|
}
|
|
331
333
|
const merged = new Float32Array(this.pending.length + samples.length);
|
|
332
334
|
merged.set(this.pending, 0);
|
|
333
335
|
merged.set(samples, this.pending.length);
|
|
334
336
|
this.pending = merged;
|
|
335
337
|
const windowSize = this.model.windowSizeSamples;
|
|
336
|
-
let event = null;
|
|
337
338
|
while (this.pending.length >= windowSize) {
|
|
338
339
|
const window = this.pending.slice(0, windowSize);
|
|
339
340
|
this.pending = this.pending.slice(windowSize);
|
|
@@ -342,10 +343,10 @@ var SileroVAD = class _SileroVAD {
|
|
|
342
343
|
const windowDuration = windowSize / this.opts.sampleRate;
|
|
343
344
|
const transition = this.advanceState(p, windowDuration);
|
|
344
345
|
if (transition !== null) {
|
|
345
|
-
|
|
346
|
+
this.eventQueue.push(transition);
|
|
346
347
|
}
|
|
347
348
|
}
|
|
348
|
-
return
|
|
349
|
+
return this.eventQueue.shift() ?? null;
|
|
349
350
|
}
|
|
350
351
|
advanceState(p, windowDuration) {
|
|
351
352
|
const opts = this.opts;
|
|
@@ -400,6 +401,7 @@ var SileroVAD = class _SileroVAD {
|
|
|
400
401
|
this.pubSpeaking = false;
|
|
401
402
|
this.speechThresholdDuration = 0;
|
|
402
403
|
this.silenceThresholdDuration = 0;
|
|
404
|
+
this.eventQueue = [];
|
|
403
405
|
this.expFilter.reset();
|
|
404
406
|
this.model.reset();
|
|
405
407
|
}
|