getpatter 0.5.4 → 0.6.1
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/LICENSE +1 -1
- package/README.md +5 -2
- package/dist/aec-PJJMUM5E.mjs +228 -0
- package/dist/{banner-3GNZ6VQK.mjs → banner-UYW6UM3J.mjs} +4 -1
- package/dist/barge-in-strategies-X6ARMGIQ.mjs +12 -0
- package/dist/{carrier-config-33HQ2W4V.mjs → carrier-config-4ZKVYAWV.mjs} +5 -2
- package/dist/{chunk-AFUYSNDH.mjs → chunk-6GR5MHHQ.mjs} +9 -0
- package/dist/chunk-CYLJVT5G.mjs +7031 -0
- package/dist/chunk-D4424JZR.mjs +71 -0
- package/dist/{chunk-VJVDG4V5.mjs → chunk-MVOQFAEO.mjs} +5 -0
- package/dist/chunk-N565J3CF.mjs +69 -0
- package/dist/chunk-RV7APPYE.mjs +397 -0
- package/dist/{chunk-FIFIWBL7.mjs → chunk-TEW3NAZJ.mjs} +6000 -3156
- package/dist/{chunk-SEMKNPCD.mjs → chunk-XS45BAQL.mjs} +5 -1
- package/dist/cli.js +304 -640
- package/dist/client-2GJVZT42.mjs +8935 -0
- package/dist/dashboard/ui.html +63 -0
- package/dist/{dist-YRCCJQ26.mjs → dist-RYMPCILF.mjs} +28 -2
- package/dist/index.d.mts +3548 -428
- package/dist/index.d.ts +3548 -428
- package/dist/index.js +34336 -9532
- package/dist/index.mjs +3642 -512
- package/dist/{node-cron-6PRPSBG5.mjs → node-cron-JFWQQRBU.mjs} +23 -2
- package/dist/persistence-LVIAHESK.mjs +7 -0
- package/dist/silero-vad-NSEXI4XS.mjs +7 -0
- package/dist/streamableHttp-WKNGHDVO.mjs +1496 -0
- package/dist/test-mode-WEKKNBLD.mjs +8 -0
- package/dist/tunnel-43CHWPVQ.mjs +8 -0
- package/package.json +7 -7
- package/src/dashboard/ui.html +63 -0
- package/dist/chunk-QHHBUCMT.mjs +0 -25
- package/dist/persistence-LQBYQPQQ.mjs +0 -7
- package/dist/test-mode-MVJ3SKG4.mjs +0 -8
- package/dist/tunnel-UVR3PPAU.mjs +0 -8
package/LICENSE
CHANGED
package/README.md
CHANGED
|
@@ -62,6 +62,7 @@ await phone.serve({ agent, tunnel: true });
|
|
|
62
62
|
| Call recording | `serve({ recording: true })` | Record all calls |
|
|
63
63
|
| Call transfer | `transfer_call` (auto-injected) | Transfer to a human |
|
|
64
64
|
| Voicemail drop | `call({ voicemailMessage: "..." })` | Play message on voicemail |
|
|
65
|
+
| Phone-as-a-tool (external agents) | `new PatterTool({ phone, agent }).execute(...)` | Drop into LangChain / OpenAI Assistants / Hermes / MCP |
|
|
65
66
|
|
|
66
67
|
## Configuration
|
|
67
68
|
|
|
@@ -182,7 +183,7 @@ import {
|
|
|
182
183
|
// Engines
|
|
183
184
|
OpenAIRealtime, ElevenLabsConvAI,
|
|
184
185
|
// STT
|
|
185
|
-
DeepgramSTT, WhisperSTT, CartesiaSTT, SonioxSTT, AssemblyAISTT,
|
|
186
|
+
DeepgramSTT, WhisperSTT, OpenAITranscribeSTT, CartesiaSTT, SonioxSTT, AssemblyAISTT,
|
|
186
187
|
// TTS
|
|
187
188
|
ElevenLabsTTS, OpenAITTS, CartesiaTTS, RimeTTS, LMNTTTS,
|
|
188
189
|
// LLM
|
|
@@ -191,6 +192,8 @@ import {
|
|
|
191
192
|
CloudflareTunnel, StaticTunnel,
|
|
192
193
|
// Primitives
|
|
193
194
|
Tool, Guardrail, tool, guardrail,
|
|
195
|
+
// Integrations
|
|
196
|
+
PatterTool,
|
|
194
197
|
} from "getpatter";
|
|
195
198
|
```
|
|
196
199
|
|
|
@@ -306,7 +309,7 @@ const agent = phone.agent({
|
|
|
306
309
|
Pull requests are welcome.
|
|
307
310
|
|
|
308
311
|
```bash
|
|
309
|
-
cd
|
|
312
|
+
cd libraries/typescript && npm install && npm test
|
|
310
313
|
```
|
|
311
314
|
|
|
312
315
|
Please open an issue before submitting large changes so we can discuss the approach first.
|
|
@@ -0,0 +1,228 @@
|
|
|
1
|
+
import {
|
|
2
|
+
init_esm_shims
|
|
3
|
+
} from "./chunk-N565J3CF.mjs";
|
|
4
|
+
|
|
5
|
+
// src/audio/aec.ts
|
|
6
|
+
init_esm_shims();
|
|
7
|
+
var DEFAULT_FILTER_TAPS = 512;
|
|
8
|
+
var DEFAULT_STEP_SIZE = 0.1;
|
|
9
|
+
var DEFAULT_WARMUP_STEP_SIZE = 0.5;
|
|
10
|
+
var DEFAULT_WARMUP_SECONDS = 0.5;
|
|
11
|
+
var DEFAULT_LEAKAGE = 0.9999;
|
|
12
|
+
var DEFAULT_DOUBLE_TALK_RHO = 0.6;
|
|
13
|
+
var FAR_END_BUFFER_SECONDS = 0.5;
|
|
14
|
+
var NlmsEchoCanceller = class {
|
|
15
|
+
taps;
|
|
16
|
+
step;
|
|
17
|
+
warmupStep;
|
|
18
|
+
warmupSamples;
|
|
19
|
+
leakage;
|
|
20
|
+
rho;
|
|
21
|
+
/** Filter coefficients — adapt to match the channel impulse response. */
|
|
22
|
+
w;
|
|
23
|
+
/** Far-end ring buffer of past TTS samples, in chronological order. */
|
|
24
|
+
farBuf;
|
|
25
|
+
farWriteIdx = 0;
|
|
26
|
+
farFilled = 0;
|
|
27
|
+
/**
|
|
28
|
+
* Sample counter used to taper the step from `warmupStep` down to
|
|
29
|
+
* `step` over the first `warmupSamples` of processed near-end audio.
|
|
30
|
+
* Counted from the first `processNearEnd` call (not construction
|
|
31
|
+
* time) so the warmup window aligns with the actual start of TTS
|
|
32
|
+
* playback rather than agent setup.
|
|
33
|
+
*/
|
|
34
|
+
processedSamples = 0;
|
|
35
|
+
/** Stats — used for diagnostics, never read on the hot path. */
|
|
36
|
+
framesProcessed = 0;
|
|
37
|
+
doubleTalkFrames = 0;
|
|
38
|
+
constructor(opts = {}) {
|
|
39
|
+
const sampleRate = opts.sampleRate ?? 16e3;
|
|
40
|
+
if (sampleRate !== 8e3 && sampleRate !== 16e3) {
|
|
41
|
+
throw new Error(
|
|
42
|
+
`NlmsEchoCanceller supports 8000 Hz or 16000 Hz only; got ${sampleRate}.`
|
|
43
|
+
);
|
|
44
|
+
}
|
|
45
|
+
const taps = opts.filterTaps ?? DEFAULT_FILTER_TAPS;
|
|
46
|
+
if (taps < 64) {
|
|
47
|
+
throw new Error(
|
|
48
|
+
`filterTaps must be >= 64 to model a meaningful echo path; got ${taps}.`
|
|
49
|
+
);
|
|
50
|
+
}
|
|
51
|
+
const step = opts.stepSize ?? DEFAULT_STEP_SIZE;
|
|
52
|
+
if (!(step > 0 && step <= 1)) {
|
|
53
|
+
throw new Error(`stepSize must be in (0, 1]; got ${step}.`);
|
|
54
|
+
}
|
|
55
|
+
const warmupStep = opts.warmupStepSize ?? DEFAULT_WARMUP_STEP_SIZE;
|
|
56
|
+
if (!(warmupStep > 0 && warmupStep <= 1)) {
|
|
57
|
+
throw new Error(
|
|
58
|
+
`warmupStepSize must be in (0, 1]; got ${warmupStep}.`
|
|
59
|
+
);
|
|
60
|
+
}
|
|
61
|
+
const warmupSeconds = opts.warmupSeconds ?? DEFAULT_WARMUP_SECONDS;
|
|
62
|
+
if (warmupSeconds < 0) {
|
|
63
|
+
throw new Error(
|
|
64
|
+
`warmupSeconds must be >= 0; got ${warmupSeconds}.`
|
|
65
|
+
);
|
|
66
|
+
}
|
|
67
|
+
const leakage = opts.leakage ?? DEFAULT_LEAKAGE;
|
|
68
|
+
if (!(leakage > 0 && leakage <= 1)) {
|
|
69
|
+
throw new Error(`leakage must be in (0, 1]; got ${leakage}.`);
|
|
70
|
+
}
|
|
71
|
+
this.taps = taps;
|
|
72
|
+
this.step = step;
|
|
73
|
+
this.warmupStep = warmupStep;
|
|
74
|
+
this.warmupSamples = Math.floor(warmupSeconds * sampleRate);
|
|
75
|
+
this.leakage = leakage;
|
|
76
|
+
this.rho = opts.doubleTalkRho ?? DEFAULT_DOUBLE_TALK_RHO;
|
|
77
|
+
this.w = new Float32Array(taps);
|
|
78
|
+
const farBufSize = Math.max(
|
|
79
|
+
taps * 2,
|
|
80
|
+
Math.floor(sampleRate * FAR_END_BUFFER_SECONDS)
|
|
81
|
+
);
|
|
82
|
+
this.farBuf = new Float32Array(farBufSize);
|
|
83
|
+
}
|
|
84
|
+
/**
|
|
85
|
+
* Append far-end (TTS) audio to the reference ring buffer.
|
|
86
|
+
*
|
|
87
|
+
* Accepts raw int16 little-endian mono PCM at the configured sample
|
|
88
|
+
* rate — same shape as what we hand off to the audio sender before the
|
|
89
|
+
* carrier-specific transcode.
|
|
90
|
+
*/
|
|
91
|
+
pushFarEnd(pcm) {
|
|
92
|
+
if (pcm.length === 0) return;
|
|
93
|
+
const samples = int16BufferToFloat32(pcm);
|
|
94
|
+
const n = samples.length;
|
|
95
|
+
const bufLen = this.farBuf.length;
|
|
96
|
+
if (n >= bufLen) {
|
|
97
|
+
this.farBuf.set(samples.subarray(n - bufLen));
|
|
98
|
+
this.farWriteIdx = 0;
|
|
99
|
+
this.farFilled = bufLen;
|
|
100
|
+
return;
|
|
101
|
+
}
|
|
102
|
+
const end = this.farWriteIdx + n;
|
|
103
|
+
if (end <= bufLen) {
|
|
104
|
+
this.farBuf.set(samples, this.farWriteIdx);
|
|
105
|
+
} else {
|
|
106
|
+
const head = bufLen - this.farWriteIdx;
|
|
107
|
+
this.farBuf.set(samples.subarray(0, head), this.farWriteIdx);
|
|
108
|
+
this.farBuf.set(samples.subarray(head), 0);
|
|
109
|
+
}
|
|
110
|
+
this.farWriteIdx = (this.farWriteIdx + n) % bufLen;
|
|
111
|
+
this.farFilled = Math.min(this.farFilled + n, bufLen);
|
|
112
|
+
}
|
|
113
|
+
/**
|
|
114
|
+
* Subtract estimated echo from the near-end (mic) signal.
|
|
115
|
+
*
|
|
116
|
+
* Returns int16 little-endian mono PCM with the estimated echo
|
|
117
|
+
* removed. When the far-end buffer hasn't been primed yet (no TTS has
|
|
118
|
+
* played) the call is a pass-through — there is nothing to cancel.
|
|
119
|
+
*/
|
|
120
|
+
processNearEnd(pcm) {
|
|
121
|
+
if (pcm.length === 0) return pcm;
|
|
122
|
+
if (this.farFilled < this.taps) return pcm;
|
|
123
|
+
const near = int16BufferToFloat32(pcm);
|
|
124
|
+
const cleaned = this.blockNlms(near);
|
|
125
|
+
this.framesProcessed += 1;
|
|
126
|
+
return float32ToInt16Buffer(cleaned);
|
|
127
|
+
}
|
|
128
|
+
/**
|
|
129
|
+
* Clear filter coefficients and far-end history.
|
|
130
|
+
*
|
|
131
|
+
* Useful between unrelated turns when the echo path may have changed
|
|
132
|
+
* (e.g. caller switched from speakerphone to handset).
|
|
133
|
+
*/
|
|
134
|
+
reset() {
|
|
135
|
+
this.w.fill(0);
|
|
136
|
+
this.farBuf.fill(0);
|
|
137
|
+
this.farWriteIdx = 0;
|
|
138
|
+
this.farFilled = 0;
|
|
139
|
+
this.processedSamples = 0;
|
|
140
|
+
this.framesProcessed = 0;
|
|
141
|
+
this.doubleTalkFrames = 0;
|
|
142
|
+
}
|
|
143
|
+
// --------------------------------------------------------------------
|
|
144
|
+
// Internals
|
|
145
|
+
// --------------------------------------------------------------------
|
|
146
|
+
/** Most-recent ``length`` far-end samples in chronological order. */
|
|
147
|
+
farWindow(length) {
|
|
148
|
+
const bufLen = this.farBuf.length;
|
|
149
|
+
const len = Math.min(length, this.farFilled);
|
|
150
|
+
const end = this.farWriteIdx;
|
|
151
|
+
if (end >= len) {
|
|
152
|
+
return this.farBuf.subarray(end - len, end);
|
|
153
|
+
}
|
|
154
|
+
const head = bufLen - (len - end);
|
|
155
|
+
const out = new Float32Array(len);
|
|
156
|
+
out.set(this.farBuf.subarray(head));
|
|
157
|
+
out.set(this.farBuf.subarray(0, end), bufLen - head);
|
|
158
|
+
return out;
|
|
159
|
+
}
|
|
160
|
+
/** Sample-by-sample NLMS over a frame of near-end samples. */
|
|
161
|
+
blockNlms(near) {
|
|
162
|
+
const taps = this.taps;
|
|
163
|
+
const desiredLen = taps + near.length - 1;
|
|
164
|
+
let farWindow = this.farWindow(desiredLen);
|
|
165
|
+
if (farWindow.length < desiredLen) {
|
|
166
|
+
const padded = new Float32Array(desiredLen);
|
|
167
|
+
padded.set(farWindow, desiredLen - farWindow.length);
|
|
168
|
+
farWindow = padded;
|
|
169
|
+
}
|
|
170
|
+
let farMax = 0;
|
|
171
|
+
for (let i = 0; i < farWindow.length; i++) {
|
|
172
|
+
const a = Math.abs(farWindow[i]);
|
|
173
|
+
if (a > farMax) farMax = a;
|
|
174
|
+
}
|
|
175
|
+
let nearMax = 0;
|
|
176
|
+
for (let i = 0; i < near.length; i++) {
|
|
177
|
+
const a = Math.abs(near[i]);
|
|
178
|
+
if (a > nearMax) nearMax = a;
|
|
179
|
+
}
|
|
180
|
+
const doubleTalk = farMax > 1e-6 ? nearMax > this.rho * farMax : false;
|
|
181
|
+
if (doubleTalk) this.doubleTalkFrames += 1;
|
|
182
|
+
const out = new Float32Array(near.length);
|
|
183
|
+
const w = this.w;
|
|
184
|
+
const leakage = this.leakage;
|
|
185
|
+
const step = this.processedSamples < this.warmupSamples ? this.warmupStep : this.step;
|
|
186
|
+
for (let i = 0; i < near.length; i++) {
|
|
187
|
+
let yEst = 0;
|
|
188
|
+
let norm = 0;
|
|
189
|
+
const base = i;
|
|
190
|
+
for (let k = 0; k < taps; k++) {
|
|
191
|
+
const xk = farWindow[base + k];
|
|
192
|
+
yEst += w[k] * xk;
|
|
193
|
+
norm += xk * xk;
|
|
194
|
+
}
|
|
195
|
+
const e = near[i] - yEst;
|
|
196
|
+
out[i] = e;
|
|
197
|
+
if (!doubleTalk) {
|
|
198
|
+
const denom = norm + 1e-6;
|
|
199
|
+
const factor = step * e / denom;
|
|
200
|
+
for (let k = 0; k < taps; k++) {
|
|
201
|
+
w[k] = leakage * w[k] + factor * farWindow[base + k];
|
|
202
|
+
}
|
|
203
|
+
}
|
|
204
|
+
}
|
|
205
|
+
this.processedSamples += near.length;
|
|
206
|
+
return out;
|
|
207
|
+
}
|
|
208
|
+
};
|
|
209
|
+
function int16BufferToFloat32(buf) {
|
|
210
|
+
const samples = new Float32Array(buf.length / 2);
|
|
211
|
+
for (let i = 0; i < samples.length; i++) {
|
|
212
|
+
samples[i] = buf.readInt16LE(i * 2) / 32768;
|
|
213
|
+
}
|
|
214
|
+
return samples;
|
|
215
|
+
}
|
|
216
|
+
function float32ToInt16Buffer(samples) {
|
|
217
|
+
const out = Buffer.alloc(samples.length * 2);
|
|
218
|
+
for (let i = 0; i < samples.length; i++) {
|
|
219
|
+
let v = Math.round(samples[i] * 32768);
|
|
220
|
+
if (v > 32767) v = 32767;
|
|
221
|
+
else if (v < -32768) v = -32768;
|
|
222
|
+
out.writeInt16LE(v, i * 2);
|
|
223
|
+
}
|
|
224
|
+
return out;
|
|
225
|
+
}
|
|
226
|
+
export {
|
|
227
|
+
NlmsEchoCanceller
|
|
228
|
+
};
|
|
@@ -1,6 +1,9 @@
|
|
|
1
|
-
import
|
|
1
|
+
import {
|
|
2
|
+
init_esm_shims
|
|
3
|
+
} from "./chunk-N565J3CF.mjs";
|
|
2
4
|
|
|
3
5
|
// src/banner.ts
|
|
6
|
+
init_esm_shims();
|
|
4
7
|
var BANNER = `
|
|
5
8
|
\u2588\u2588\u2588\u2588\u2588\u2588\u2557 \u2588\u2588\u2588\u2588\u2588\u2557 \u2588\u2588\u2588\u2588\u2588\u2588\u2588\u2588\u2557\u2588\u2588\u2588\u2588\u2588\u2588\u2588\u2588\u2557\u2588\u2588\u2588\u2588\u2588\u2588\u2588\u2557\u2588\u2588\u2588\u2588\u2588\u2588\u2557
|
|
6
9
|
\u2588\u2588\u2554\u2550\u2550\u2588\u2588\u2557\u2588\u2588\u2554\u2550\u2550\u2588\u2588\u2557\u255A\u2550\u2550\u2588\u2588\u2554\u2550\u2550\u255D\u255A\u2550\u2550\u2588\u2588\u2554\u2550\u2550\u255D\u2588\u2588\u2554\u2550\u2550\u2550\u2550\u255D\u2588\u2588\u2554\u2550\u2550\u2588\u2588\u2557
|
|
@@ -1,9 +1,12 @@
|
|
|
1
1
|
import {
|
|
2
2
|
getLogger
|
|
3
|
-
} from "./chunk-
|
|
4
|
-
import
|
|
3
|
+
} from "./chunk-MVOQFAEO.mjs";
|
|
4
|
+
import {
|
|
5
|
+
init_esm_shims
|
|
6
|
+
} from "./chunk-N565J3CF.mjs";
|
|
5
7
|
|
|
6
8
|
// src/carrier-config.ts
|
|
9
|
+
init_esm_shims();
|
|
7
10
|
var TWILIO_API_BASE = "https://api.twilio.com/2010-04-01";
|
|
8
11
|
var TELNYX_API_BASE = "https://api.telnyx.com/v2";
|
|
9
12
|
async function configureTwilioNumber(accountSid, authToken, phoneNumber, voiceUrl) {
|
|
@@ -1,6 +1,15 @@
|
|
|
1
|
+
import {
|
|
2
|
+
init_esm_shims
|
|
3
|
+
} from "./chunk-N565J3CF.mjs";
|
|
4
|
+
|
|
1
5
|
// src/dashboard/persistence.ts
|
|
6
|
+
init_esm_shims();
|
|
2
7
|
import http from "http";
|
|
3
8
|
function notifyDashboard(callData, port = 8e3) {
|
|
9
|
+
const flag = (process.env.PATTER_DASHBOARD_NOTIFY ?? "").trim().toLowerCase();
|
|
10
|
+
if (flag === "0" || flag === "false" || flag === "no" || flag === "off") {
|
|
11
|
+
return;
|
|
12
|
+
}
|
|
4
13
|
try {
|
|
5
14
|
const body = JSON.stringify(callData);
|
|
6
15
|
const req = http.request(
|