@kodelyth/voice-call 2026.5.42 → 2026.6.2
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/package.json +18 -6
- package/api.ts +0 -16
- package/cli-metadata.ts +0 -10
- package/config-api.ts +0 -12
- package/index.test.ts +0 -1075
- package/index.ts +0 -863
- package/runtime-api.ts +0 -20
- package/runtime-entry.ts +0 -1
- package/setup-api.ts +0 -47
- package/src/allowlist.test.ts +0 -18
- package/src/allowlist.ts +0 -19
- package/src/cli.test.ts +0 -12
- package/src/cli.ts +0 -866
- package/src/config-compat.test.ts +0 -130
- package/src/config-compat.ts +0 -227
- package/src/config.test.ts +0 -542
- package/src/config.ts +0 -883
- package/src/core-bridge.ts +0 -14
- package/src/deep-merge.test.ts +0 -40
- package/src/deep-merge.ts +0 -23
- package/src/gateway-continue-operation.ts +0 -200
- package/src/http-headers.test.ts +0 -16
- package/src/http-headers.ts +0 -15
- package/src/manager/context.ts +0 -50
- package/src/manager/events.test.ts +0 -578
- package/src/manager/events.ts +0 -332
- package/src/manager/lifecycle.ts +0 -53
- package/src/manager/lookup.test.ts +0 -52
- package/src/manager/lookup.ts +0 -35
- package/src/manager/outbound.test.ts +0 -629
- package/src/manager/outbound.ts +0 -508
- package/src/manager/state.ts +0 -48
- package/src/manager/store.ts +0 -107
- package/src/manager/timers.test.ts +0 -127
- package/src/manager/timers.ts +0 -113
- package/src/manager/twiml.test.ts +0 -13
- package/src/manager/twiml.ts +0 -17
- package/src/manager.closed-loop.test.ts +0 -259
- package/src/manager.inbound-allowlist.test.ts +0 -183
- package/src/manager.notify.test.ts +0 -390
- package/src/manager.restore.test.ts +0 -310
- package/src/manager.test-harness.ts +0 -127
- package/src/manager.ts +0 -441
- package/src/media-stream.test.ts +0 -953
- package/src/media-stream.ts +0 -876
- package/src/providers/base.ts +0 -99
- package/src/providers/mock.test.ts +0 -86
- package/src/providers/mock.ts +0 -185
- package/src/providers/plivo.test.ts +0 -93
- package/src/providers/plivo.ts +0 -601
- package/src/providers/shared/call-status.test.ts +0 -24
- package/src/providers/shared/call-status.ts +0 -24
- package/src/providers/shared/guarded-json-api.test.ts +0 -127
- package/src/providers/shared/guarded-json-api.ts +0 -49
- package/src/providers/telnyx.test.ts +0 -489
- package/src/providers/telnyx.ts +0 -419
- package/src/providers/twilio/api.test.ts +0 -184
- package/src/providers/twilio/api.ts +0 -100
- package/src/providers/twilio/twiml-policy.test.ts +0 -84
- package/src/providers/twilio/twiml-policy.ts +0 -87
- package/src/providers/twilio/webhook.ts +0 -34
- package/src/providers/twilio.test.ts +0 -607
- package/src/providers/twilio.ts +0 -861
- package/src/providers/twilio.types.ts +0 -17
- package/src/realtime-agent-context.test.ts +0 -101
- package/src/realtime-agent-context.ts +0 -149
- package/src/realtime-defaults.ts +0 -3
- package/src/realtime-fast-context.test.ts +0 -74
- package/src/realtime-fast-context.ts +0 -27
- package/src/realtime-transcription.runtime.ts +0 -4
- package/src/realtime-voice.runtime.ts +0 -5
- package/src/response-generator.test.ts +0 -385
- package/src/response-generator.ts +0 -348
- package/src/response-model.test.ts +0 -71
- package/src/response-model.ts +0 -23
- package/src/runtime.test.ts +0 -625
- package/src/runtime.ts +0 -528
- package/src/telephony-audio.test.ts +0 -61
- package/src/telephony-audio.ts +0 -12
- package/src/telephony-tts.test.ts +0 -196
- package/src/telephony-tts.ts +0 -235
- package/src/test-fixtures.ts +0 -82
- package/src/tts-provider-voice.test.ts +0 -34
- package/src/tts-provider-voice.ts +0 -21
- package/src/tunnel.test.ts +0 -173
- package/src/tunnel.ts +0 -314
- package/src/types.ts +0 -311
- package/src/utils.test.ts +0 -17
- package/src/utils.ts +0 -14
- package/src/voice-mapping.test.ts +0 -32
- package/src/voice-mapping.ts +0 -65
- package/src/webhook/realtime-audio-pacer.test.ts +0 -146
- package/src/webhook/realtime-audio-pacer.ts +0 -204
- package/src/webhook/realtime-handler.test.ts +0 -1450
- package/src/webhook/realtime-handler.ts +0 -1382
- package/src/webhook/stale-call-reaper.test.ts +0 -89
- package/src/webhook/stale-call-reaper.ts +0 -38
- package/src/webhook/stream-frame-adapter.test.ts +0 -187
- package/src/webhook/stream-frame-adapter.ts +0 -219
- package/src/webhook/tailscale.test.ts +0 -216
- package/src/webhook/tailscale.ts +0 -129
- package/src/webhook-exposure.test.ts +0 -33
- package/src/webhook-exposure.ts +0 -84
- package/src/webhook-security.test.ts +0 -813
- package/src/webhook-security.ts +0 -982
- package/src/webhook.hangup-once.lifecycle.test.ts +0 -179
- package/src/webhook.test.ts +0 -1615
- package/src/webhook.ts +0 -933
- package/src/webhook.types.ts +0 -5
- package/src/websocket-test-support.ts +0 -72
- package/tsconfig.json +0 -16
|
@@ -1,204 +0,0 @@
|
|
|
1
|
-
const TELEPHONY_SAMPLE_RATE = 8_000;
|
|
2
|
-
const TELEPHONY_CHUNK_BYTES = 160;
|
|
3
|
-
const TELEPHONY_CHUNK_MS = 20;
|
|
4
|
-
const DEFAULT_SPEECH_RMS_THRESHOLD = 0.035;
|
|
5
|
-
const DEFAULT_REQUIRED_LOUD_CHUNKS = 4;
|
|
6
|
-
const DEFAULT_REQUIRED_QUIET_CHUNKS = 12;
|
|
7
|
-
const DEFAULT_MAX_QUEUED_AUDIO_BYTES = TELEPHONY_SAMPLE_RATE * 120;
|
|
8
|
-
const PCM16_MAX_AMPLITUDE = 32768;
|
|
9
|
-
const MULAW_LINEAR_SAMPLES = new Int16Array(256);
|
|
10
|
-
|
|
11
|
-
for (let i = 0; i < MULAW_LINEAR_SAMPLES.length; i += 1) {
|
|
12
|
-
MULAW_LINEAR_SAMPLES[i] = decodeMulawSample(i);
|
|
13
|
-
}
|
|
14
|
-
|
|
15
|
-
type RealtimeAudioQueueItem =
|
|
16
|
-
| {
|
|
17
|
-
chunk: Buffer;
|
|
18
|
-
durationMs: number;
|
|
19
|
-
type: "audio";
|
|
20
|
-
}
|
|
21
|
-
| {
|
|
22
|
-
name: string;
|
|
23
|
-
type: "mark";
|
|
24
|
-
};
|
|
25
|
-
|
|
26
|
-
export type RealtimeAudioSend = (message: string) => boolean;
|
|
27
|
-
|
|
28
|
-
export interface RealtimeAudioSerializer {
|
|
29
|
-
media(payloadBase64: string): string;
|
|
30
|
-
clear(): string;
|
|
31
|
-
mark(name: string): string;
|
|
32
|
-
}
|
|
33
|
-
|
|
34
|
-
export class RealtimeAudioPacer {
|
|
35
|
-
private queue: RealtimeAudioQueueItem[] = [];
|
|
36
|
-
private timer: ReturnType<typeof setTimeout> | null = null;
|
|
37
|
-
private queuedAudioBytes = 0;
|
|
38
|
-
private closed = false;
|
|
39
|
-
|
|
40
|
-
constructor(
|
|
41
|
-
private readonly params: {
|
|
42
|
-
maxQueuedAudioBytes?: number;
|
|
43
|
-
onBackpressure?: () => void;
|
|
44
|
-
send: RealtimeAudioSend;
|
|
45
|
-
serializer: RealtimeAudioSerializer;
|
|
46
|
-
},
|
|
47
|
-
) {}
|
|
48
|
-
|
|
49
|
-
sendAudio(muLaw: Buffer): void {
|
|
50
|
-
if (this.closed || muLaw.length === 0) {
|
|
51
|
-
return;
|
|
52
|
-
}
|
|
53
|
-
const maxQueuedAudioBytes = this.params.maxQueuedAudioBytes ?? DEFAULT_MAX_QUEUED_AUDIO_BYTES;
|
|
54
|
-
for (let offset = 0; offset < muLaw.length; offset += TELEPHONY_CHUNK_BYTES) {
|
|
55
|
-
const chunk = Buffer.from(muLaw.subarray(offset, offset + TELEPHONY_CHUNK_BYTES));
|
|
56
|
-
if (this.queuedAudioBytes + chunk.length > maxQueuedAudioBytes) {
|
|
57
|
-
this.failBackpressure();
|
|
58
|
-
return;
|
|
59
|
-
}
|
|
60
|
-
this.queue.push({
|
|
61
|
-
type: "audio",
|
|
62
|
-
chunk,
|
|
63
|
-
durationMs: Math.max(1, Math.round((chunk.length / TELEPHONY_SAMPLE_RATE) * 1000)),
|
|
64
|
-
});
|
|
65
|
-
this.queuedAudioBytes += chunk.length;
|
|
66
|
-
}
|
|
67
|
-
this.ensurePump();
|
|
68
|
-
}
|
|
69
|
-
|
|
70
|
-
sendMark(name: string): void {
|
|
71
|
-
if (this.closed || !name) {
|
|
72
|
-
return;
|
|
73
|
-
}
|
|
74
|
-
this.queue.push({ type: "mark", name });
|
|
75
|
-
this.ensurePump();
|
|
76
|
-
}
|
|
77
|
-
|
|
78
|
-
clearAudio(): number {
|
|
79
|
-
if (this.closed) {
|
|
80
|
-
return 0;
|
|
81
|
-
}
|
|
82
|
-
const clearedAudioBytes = this.queuedAudioBytes;
|
|
83
|
-
this.clearTimer();
|
|
84
|
-
this.queue = [];
|
|
85
|
-
this.queuedAudioBytes = 0;
|
|
86
|
-
this.params.send(this.params.serializer.clear());
|
|
87
|
-
return clearedAudioBytes;
|
|
88
|
-
}
|
|
89
|
-
|
|
90
|
-
close(): void {
|
|
91
|
-
this.closed = true;
|
|
92
|
-
this.clearTimer();
|
|
93
|
-
this.queue = [];
|
|
94
|
-
this.queuedAudioBytes = 0;
|
|
95
|
-
}
|
|
96
|
-
|
|
97
|
-
private clearTimer(): void {
|
|
98
|
-
if (!this.timer) {
|
|
99
|
-
return;
|
|
100
|
-
}
|
|
101
|
-
clearTimeout(this.timer);
|
|
102
|
-
this.timer = null;
|
|
103
|
-
}
|
|
104
|
-
|
|
105
|
-
private ensurePump(): void {
|
|
106
|
-
if (!this.timer) {
|
|
107
|
-
this.pump();
|
|
108
|
-
}
|
|
109
|
-
}
|
|
110
|
-
|
|
111
|
-
private failBackpressure(): void {
|
|
112
|
-
this.close();
|
|
113
|
-
this.params.onBackpressure?.();
|
|
114
|
-
}
|
|
115
|
-
|
|
116
|
-
private pump(): void {
|
|
117
|
-
this.timer = null;
|
|
118
|
-
if (this.closed) {
|
|
119
|
-
return;
|
|
120
|
-
}
|
|
121
|
-
const item = this.queue.shift();
|
|
122
|
-
if (!item) {
|
|
123
|
-
return;
|
|
124
|
-
}
|
|
125
|
-
|
|
126
|
-
let delayMs = 0;
|
|
127
|
-
let sent = true;
|
|
128
|
-
if (item.type === "audio") {
|
|
129
|
-
this.queuedAudioBytes = Math.max(0, this.queuedAudioBytes - item.chunk.length);
|
|
130
|
-
sent = this.params.send(this.params.serializer.media(item.chunk.toString("base64")));
|
|
131
|
-
delayMs = item.durationMs || TELEPHONY_CHUNK_MS;
|
|
132
|
-
} else {
|
|
133
|
-
sent = this.params.send(this.params.serializer.mark(item.name));
|
|
134
|
-
}
|
|
135
|
-
|
|
136
|
-
if (!sent) {
|
|
137
|
-
this.queue = [];
|
|
138
|
-
this.queuedAudioBytes = 0;
|
|
139
|
-
return;
|
|
140
|
-
}
|
|
141
|
-
if (this.queue.length > 0) {
|
|
142
|
-
this.timer = setTimeout(() => this.pump(), delayMs);
|
|
143
|
-
}
|
|
144
|
-
}
|
|
145
|
-
}
|
|
146
|
-
|
|
147
|
-
export function calculateMulawRms(muLaw: Buffer): number {
|
|
148
|
-
if (muLaw.length === 0) {
|
|
149
|
-
return 0;
|
|
150
|
-
}
|
|
151
|
-
let sum = 0;
|
|
152
|
-
for (let i = 0; i < muLaw.length; i += 1) {
|
|
153
|
-
const normalized = (MULAW_LINEAR_SAMPLES[muLaw[i] ?? 0] ?? 0) / PCM16_MAX_AMPLITUDE;
|
|
154
|
-
sum += normalized * normalized;
|
|
155
|
-
}
|
|
156
|
-
return Math.sqrt(sum / muLaw.length);
|
|
157
|
-
}
|
|
158
|
-
|
|
159
|
-
export class RealtimeMulawSpeechStartDetector {
|
|
160
|
-
private loudChunks = 0;
|
|
161
|
-
private quietChunks = DEFAULT_REQUIRED_QUIET_CHUNKS;
|
|
162
|
-
private speaking = false;
|
|
163
|
-
|
|
164
|
-
constructor(
|
|
165
|
-
private readonly params: {
|
|
166
|
-
requiredLoudChunks?: number;
|
|
167
|
-
requiredQuietChunks?: number;
|
|
168
|
-
rmsThreshold?: number;
|
|
169
|
-
} = {},
|
|
170
|
-
) {}
|
|
171
|
-
|
|
172
|
-
accept(muLaw: Buffer): boolean {
|
|
173
|
-
const rms = calculateMulawRms(muLaw);
|
|
174
|
-
const threshold = this.params.rmsThreshold ?? DEFAULT_SPEECH_RMS_THRESHOLD;
|
|
175
|
-
if (rms >= threshold) {
|
|
176
|
-
this.quietChunks = 0;
|
|
177
|
-
this.loudChunks += 1;
|
|
178
|
-
const requiredLoudChunks = this.params.requiredLoudChunks ?? DEFAULT_REQUIRED_LOUD_CHUNKS;
|
|
179
|
-
if (!this.speaking && this.loudChunks >= requiredLoudChunks) {
|
|
180
|
-
this.speaking = true;
|
|
181
|
-
return true;
|
|
182
|
-
}
|
|
183
|
-
return false;
|
|
184
|
-
}
|
|
185
|
-
|
|
186
|
-
this.loudChunks = 0;
|
|
187
|
-
this.quietChunks += 1;
|
|
188
|
-
const requiredQuietChunks = this.params.requiredQuietChunks ?? DEFAULT_REQUIRED_QUIET_CHUNKS;
|
|
189
|
-
if (this.quietChunks >= requiredQuietChunks) {
|
|
190
|
-
this.speaking = false;
|
|
191
|
-
}
|
|
192
|
-
return false;
|
|
193
|
-
}
|
|
194
|
-
}
|
|
195
|
-
|
|
196
|
-
function decodeMulawSample(value: number): number {
|
|
197
|
-
const muLaw = ~value & 0xff;
|
|
198
|
-
const sign = muLaw & 0x80;
|
|
199
|
-
const exponent = (muLaw >> 4) & 0x07;
|
|
200
|
-
const mantissa = muLaw & 0x0f;
|
|
201
|
-
let sample = ((mantissa << 3) + 132) << exponent;
|
|
202
|
-
sample -= 132;
|
|
203
|
-
return sign ? -sample : sample;
|
|
204
|
-
}
|