@kuralle-syrinx/server-websocket 4.4.0 → 4.5.0
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/backchannel-cue-fixtures.d.ts +4 -0
- package/dist/backchannel-cue-fixtures.d.ts.map +1 -0
- package/dist/backchannel-cue-fixtures.js +25 -0
- package/dist/backchannel-cue-fixtures.js.map +1 -0
- package/dist/background-audio.d.ts +59 -0
- package/dist/background-audio.d.ts.map +1 -0
- package/dist/background-audio.js +248 -0
- package/dist/background-audio.js.map +1 -0
- package/dist/browser-opus.d.ts +12 -0
- package/dist/browser-opus.d.ts.map +1 -0
- package/dist/browser-opus.js +48 -0
- package/dist/browser-opus.js.map +1 -0
- package/dist/carrier-commands.d.ts +124 -0
- package/dist/carrier-commands.d.ts.map +1 -0
- package/dist/carrier-commands.js +193 -0
- package/dist/carrier-commands.js.map +1 -0
- package/dist/edge-telnyx.d.ts +37 -0
- package/dist/edge-telnyx.d.ts.map +1 -0
- package/dist/edge-telnyx.js +320 -0
- package/dist/edge-telnyx.js.map +1 -0
- package/dist/edge-twilio.d.ts +31 -0
- package/dist/edge-twilio.d.ts.map +1 -0
- package/dist/edge-twilio.js +286 -0
- package/dist/edge-twilio.js.map +1 -0
- package/dist/edge.d.ts +71 -0
- package/dist/edge.d.ts.map +1 -0
- package/dist/edge.js +560 -0
- package/dist/edge.js.map +1 -0
- package/dist/inbound-audio.d.ts +13 -0
- package/dist/inbound-audio.d.ts.map +1 -0
- package/dist/inbound-audio.js +66 -0
- package/dist/inbound-audio.js.map +1 -0
- package/dist/index.d.ts +81 -0
- package/dist/index.d.ts.map +1 -0
- package/dist/index.js +718 -0
- package/dist/index.js.map +1 -0
- package/dist/json-message.d.ts +7 -0
- package/dist/json-message.d.ts.map +1 -0
- package/dist/json-message.js +41 -0
- package/dist/json-message.js.map +1 -0
- package/dist/outbound-playout-pipeline.d.ts +53 -0
- package/dist/outbound-playout-pipeline.d.ts.map +1 -0
- package/dist/outbound-playout-pipeline.js +148 -0
- package/dist/outbound-playout-pipeline.js.map +1 -0
- package/dist/paced-playout.d.ts +42 -0
- package/dist/paced-playout.d.ts.map +1 -0
- package/dist/paced-playout.js +151 -0
- package/dist/paced-playout.js.map +1 -0
- package/dist/playout-progress.d.ts +26 -0
- package/dist/playout-progress.d.ts.map +1 -0
- package/dist/playout-progress.js +64 -0
- package/dist/playout-progress.js.map +1 -0
- package/dist/session-store.d.ts +39 -0
- package/dist/session-store.d.ts.map +1 -0
- package/dist/session-store.js +80 -0
- package/dist/session-store.js.map +1 -0
- package/dist/smartpbx.d.ts +47 -0
- package/dist/smartpbx.d.ts.map +1 -0
- package/dist/smartpbx.js +437 -0
- package/dist/smartpbx.js.map +1 -0
- package/dist/telnyx-codec.d.ts +41 -0
- package/dist/telnyx-codec.d.ts.map +1 -0
- package/dist/telnyx-codec.js +114 -0
- package/dist/telnyx-codec.js.map +1 -0
- package/dist/telnyx.d.ts +52 -0
- package/dist/telnyx.d.ts.map +1 -0
- package/dist/telnyx.js +550 -0
- package/dist/telnyx.js.map +1 -0
- package/dist/test-helpers.d.ts +20 -0
- package/dist/test-helpers.d.ts.map +1 -0
- package/dist/test-helpers.js +242 -0
- package/dist/test-helpers.js.map +1 -0
- package/dist/transport-helpers.d.ts +12 -0
- package/dist/transport-helpers.d.ts.map +1 -0
- package/dist/transport-helpers.js +90 -0
- package/dist/transport-helpers.js.map +1 -0
- package/dist/transport-host.d.ts +56 -0
- package/dist/transport-host.d.ts.map +1 -0
- package/dist/transport-host.js +110 -0
- package/dist/transport-host.js.map +1 -0
- package/dist/turn-metrics.d.ts +79 -0
- package/dist/turn-metrics.d.ts.map +1 -0
- package/dist/turn-metrics.js +208 -0
- package/dist/turn-metrics.js.map +1 -0
- package/dist/twilio-auth.d.ts +12 -0
- package/dist/twilio-auth.d.ts.map +1 -0
- package/dist/twilio-auth.js +44 -0
- package/dist/twilio-auth.js.map +1 -0
- package/dist/twilio.d.ts +56 -0
- package/dist/twilio.d.ts.map +1 -0
- package/dist/twilio.js +511 -0
- package/dist/twilio.js.map +1 -0
- package/dist/websocket-close.d.ts +12 -0
- package/dist/websocket-close.d.ts.map +1 -0
- package/dist/websocket-close.js +58 -0
- package/dist/websocket-close.js.map +1 -0
- package/dist/websocket-lifecycle.d.ts +29 -0
- package/dist/websocket-lifecycle.d.ts.map +1 -0
- package/dist/websocket-lifecycle.js +87 -0
- package/dist/websocket-lifecycle.js.map +1 -0
- package/dist/websocket-upgrade.d.ts +9 -0
- package/dist/websocket-upgrade.d.ts.map +1 -0
- package/dist/websocket-upgrade.js +91 -0
- package/dist/websocket-upgrade.js.map +1 -0
- package/dist/wire-carrier-control.d.ts +27 -0
- package/dist/wire-carrier-control.d.ts.map +1 -0
- package/dist/wire-carrier-control.js +116 -0
- package/dist/wire-carrier-control.js.map +1 -0
- package/package.json +32 -11
- package/src/admission-control.test.ts +270 -0
- package/src/background-audio.test.ts +224 -0
- package/src/browser-opus.test.ts +41 -0
- package/src/browser-pacing.test.ts +440 -0
- package/src/carrier-commands.test.ts +198 -0
- package/src/edge-telnyx.test.ts +333 -0
- package/src/edge-twilio.test.ts +293 -0
- package/src/edge.test.ts +727 -0
- package/src/graceful-drain.test.ts +306 -0
- package/src/inbound-audio.test.ts +58 -0
- package/src/index.test.ts +2078 -0
- package/src/outbound-playout-pipeline.test.ts +314 -0
- package/src/paced-playout.test.ts +247 -0
- package/src/playout-progress.test.ts +56 -0
- package/src/session-store.test.ts +274 -0
- package/src/smartpbx.test.ts +986 -0
- package/src/telnyx.test.ts +1633 -0
- package/src/transport-host.test.ts +51 -0
- package/src/turn-metrics.test.ts +503 -0
- package/src/twilio-auth.test.ts +36 -0
- package/src/twilio.test.ts +1274 -0
- package/src/websocket-close.test.ts +63 -0
- package/src/websocket-lifecycle.test.ts +49 -0
|
@@ -0,0 +1,193 @@
|
|
|
1
|
+
// SPDX-License-Identifier: MIT
|
|
2
|
+
//
|
|
3
|
+
// Pure carrier out-of-band command constructors + injectable fetch dispatchers.
|
|
4
|
+
// Workers-safe: TypedArray-free pure objects + global `fetch()` only.
|
|
5
|
+
// No Node APIs, no process.env — credentials and call handles are injected.
|
|
6
|
+
//
|
|
7
|
+
// HONESTY: payload shapes unit-tested. Live HTTP dispatch to Twilio/Telnyx is
|
|
8
|
+
// unverified against a live carrier (no credentials in this build). Real IVR
|
|
9
|
+
// DTMF decode, trunk G.722 negotiation, and live transfer bridge remain
|
|
10
|
+
// carrier-gated.
|
|
11
|
+
/**
|
|
12
|
+
* Twilio DTMF via Calls API `SendDigits` / TwiML `<Play digits="...">`.
|
|
13
|
+
* Pause letters `w`/`W` pass through unchanged (Twilio's native pause syntax).
|
|
14
|
+
* Live dispatch unverified.
|
|
15
|
+
*/
|
|
16
|
+
export function buildTwilioSendDigits(callSid, packet) {
|
|
17
|
+
const digits = packet.digits;
|
|
18
|
+
return {
|
|
19
|
+
carrier: "twilio",
|
|
20
|
+
kind: "send_digits",
|
|
21
|
+
path: `/Calls/${encodeURIComponent(callSid)}.json`,
|
|
22
|
+
method: "POST",
|
|
23
|
+
form: { SendDigits: digits },
|
|
24
|
+
twiml: `<Response><Play digits="${escapeXml(digits)}"/></Response>`,
|
|
25
|
+
};
|
|
26
|
+
}
|
|
27
|
+
/**
|
|
28
|
+
* Telnyx Call Control `send_dtmf`. Pause letters retained in the digits string.
|
|
29
|
+
* Live dispatch unverified.
|
|
30
|
+
*/
|
|
31
|
+
export function buildTelnyxSendDtmf(callControlId, packet, opts) {
|
|
32
|
+
return {
|
|
33
|
+
carrier: "telnyx",
|
|
34
|
+
kind: "send_dtmf",
|
|
35
|
+
path: `/v2/calls/${encodeURIComponent(callControlId)}/actions/send_dtmf`,
|
|
36
|
+
method: "POST",
|
|
37
|
+
json: {
|
|
38
|
+
digits: packet.digits,
|
|
39
|
+
...(opts?.durationMillis !== undefined ? { duration_millis: opts.durationMillis } : {}),
|
|
40
|
+
},
|
|
41
|
+
};
|
|
42
|
+
}
|
|
43
|
+
/**
|
|
44
|
+
* Twilio transfer via Calls API Twiml/Url redirect.
|
|
45
|
+
* Prefer `<Dial>` (Call-Control style) over SIP REFER — REFER drops STIR/SHAKEN
|
|
46
|
+
* attestation to B and hurts answer rate.
|
|
47
|
+
* Live dispatch unverified.
|
|
48
|
+
*/
|
|
49
|
+
export function buildTwilioTransfer(callSid, packet, opts) {
|
|
50
|
+
const twiml = twilioTransferTwiml(packet);
|
|
51
|
+
return {
|
|
52
|
+
carrier: "twilio",
|
|
53
|
+
kind: "transfer",
|
|
54
|
+
mode: packet.mode,
|
|
55
|
+
path: `/Calls/${encodeURIComponent(callSid)}.json`,
|
|
56
|
+
method: "POST",
|
|
57
|
+
form: opts?.redirectUrl
|
|
58
|
+
? { Url: opts.redirectUrl, Method: "POST" }
|
|
59
|
+
: { Twiml: twiml },
|
|
60
|
+
twiml,
|
|
61
|
+
...(packet.summary !== undefined ? { summary: packet.summary } : {}),
|
|
62
|
+
};
|
|
63
|
+
}
|
|
64
|
+
/**
|
|
65
|
+
* Telnyx Call Control `transfer`. Prefer this over SIP REFER (mode sip_refer still
|
|
66
|
+
* uses the transfer action with the SIP URI as `to` — Call Control, not raw REFER).
|
|
67
|
+
* Live dispatch unverified.
|
|
68
|
+
*/
|
|
69
|
+
export function buildTelnyxTransfer(callControlId, packet, opts) {
|
|
70
|
+
// Warm summary is not a Telnyx wire field — surface via client_state so a
|
|
71
|
+
// receiving reasoner-seam can recover it after the bridge.
|
|
72
|
+
const clientState = packet.mode === "warm" && packet.summary
|
|
73
|
+
? base64UrlEncode(JSON.stringify({ summary: packet.summary, mode: "warm" }))
|
|
74
|
+
: undefined;
|
|
75
|
+
return {
|
|
76
|
+
carrier: "telnyx",
|
|
77
|
+
kind: "transfer",
|
|
78
|
+
mode: packet.mode,
|
|
79
|
+
path: `/v2/calls/${encodeURIComponent(callControlId)}/actions/transfer`,
|
|
80
|
+
method: "POST",
|
|
81
|
+
json: {
|
|
82
|
+
to: packet.target,
|
|
83
|
+
...(opts?.commandId ? { command_id: opts.commandId } : {}),
|
|
84
|
+
...(clientState ? { client_state: clientState } : {}),
|
|
85
|
+
...(opts?.webhookUrl ? { webhook_url: opts.webhookUrl } : {}),
|
|
86
|
+
},
|
|
87
|
+
...(packet.summary !== undefined ? { summary: packet.summary } : {}),
|
|
88
|
+
};
|
|
89
|
+
}
|
|
90
|
+
function twilioTransferTwiml(packet) {
|
|
91
|
+
// sip_refer still uses Dial — we deliberately avoid raw SIP REFER for answer-rate.
|
|
92
|
+
const target = escapeXml(packet.target);
|
|
93
|
+
if (packet.target.startsWith("sip:")) {
|
|
94
|
+
return `<Response><Dial><Sip>${target}</Sip></Dial></Response>`;
|
|
95
|
+
}
|
|
96
|
+
const warmNote = packet.mode === "warm" && packet.summary
|
|
97
|
+
? `<!-- warm-handoff: ${escapeXml(packet.summary)} -->`
|
|
98
|
+
: "";
|
|
99
|
+
return `<Response>${warmNote}<Dial>${target}</Dial></Response>`;
|
|
100
|
+
}
|
|
101
|
+
/**
|
|
102
|
+
* Dispatch a Twilio form POST via global `fetch()`. Credentials injected —
|
|
103
|
+
* never reads process.env (Workers-safe). Live call unverified.
|
|
104
|
+
*/
|
|
105
|
+
export async function dispatchTwilioCommand(creds, command, fetchImpl = globalThis.fetch.bind(globalThis)) {
|
|
106
|
+
const base = creds.baseUrl ??
|
|
107
|
+
`https://api.twilio.com/2010-04-01/Accounts/${encodeURIComponent(creds.accountSid)}`;
|
|
108
|
+
const url = `${base.replace(/\/$/, "")}${command.path}`;
|
|
109
|
+
const body = new URLSearchParams(command.form).toString();
|
|
110
|
+
const auth = basicAuth(creds.accountSid, creds.authToken);
|
|
111
|
+
return fetchImpl(url, {
|
|
112
|
+
method: command.method,
|
|
113
|
+
headers: {
|
|
114
|
+
Authorization: auth,
|
|
115
|
+
"Content-Type": "application/x-www-form-urlencoded",
|
|
116
|
+
},
|
|
117
|
+
body,
|
|
118
|
+
});
|
|
119
|
+
}
|
|
120
|
+
/**
|
|
121
|
+
* Dispatch a Telnyx Call Control JSON POST via global `fetch()`. Credentials
|
|
122
|
+
* injected — never reads process.env (Workers-safe). Live call unverified.
|
|
123
|
+
*/
|
|
124
|
+
export async function dispatchTelnyxCommand(creds, command, fetchImpl = globalThis.fetch.bind(globalThis)) {
|
|
125
|
+
const base = (creds.baseUrl ?? "https://api.telnyx.com").replace(/\/$/, "");
|
|
126
|
+
const url = `${base}${command.path}`;
|
|
127
|
+
return fetchImpl(url, {
|
|
128
|
+
method: command.method,
|
|
129
|
+
headers: {
|
|
130
|
+
Authorization: `Bearer ${creds.apiKey}`,
|
|
131
|
+
"Content-Type": "application/json",
|
|
132
|
+
Accept: "application/json",
|
|
133
|
+
},
|
|
134
|
+
body: JSON.stringify(command.json),
|
|
135
|
+
});
|
|
136
|
+
}
|
|
137
|
+
/** Resolve summary: packet.summary wins; else optional summarizer; else undefined. */
|
|
138
|
+
export async function resolveTransferSummary(packet, summarizer) {
|
|
139
|
+
if (packet.summary !== undefined && packet.summary !== "")
|
|
140
|
+
return packet.summary;
|
|
141
|
+
if (packet.mode !== "warm" || !summarizer)
|
|
142
|
+
return packet.summary;
|
|
143
|
+
return summarizer({ contextId: packet.contextId, target: packet.target });
|
|
144
|
+
}
|
|
145
|
+
// ── helpers ─────────────────────────────────────────────────────────────────
|
|
146
|
+
function escapeXml(value) {
|
|
147
|
+
return value
|
|
148
|
+
.replace(/&/g, "&")
|
|
149
|
+
.replace(/</g, "<")
|
|
150
|
+
.replace(/>/g, ">")
|
|
151
|
+
.replace(/"/g, """)
|
|
152
|
+
.replace(/'/g, "'");
|
|
153
|
+
}
|
|
154
|
+
function basicAuth(user, pass) {
|
|
155
|
+
// btoa works on Workers + Node 18+; fall back to Buffer only if btoa missing
|
|
156
|
+
// (should not happen on our targets — kept pure when btoa exists).
|
|
157
|
+
const token = `${user}:${pass}`;
|
|
158
|
+
if (typeof globalThis.btoa === "function") {
|
|
159
|
+
return `Basic ${globalThis.btoa(token)}`;
|
|
160
|
+
}
|
|
161
|
+
const bytes = new TextEncoder().encode(token);
|
|
162
|
+
let bin = "";
|
|
163
|
+
for (let i = 0; i < bytes.length; i += 1)
|
|
164
|
+
bin += String.fromCharCode(bytes[i]);
|
|
165
|
+
// Minimal base64 without Buffer (Workers-safe path if btoa absent)
|
|
166
|
+
return `Basic ${base64Encode(bin)}`;
|
|
167
|
+
}
|
|
168
|
+
function base64UrlEncode(text) {
|
|
169
|
+
const bytes = new TextEncoder().encode(text);
|
|
170
|
+
let bin = "";
|
|
171
|
+
for (let i = 0; i < bytes.length; i += 1)
|
|
172
|
+
bin += String.fromCharCode(bytes[i]);
|
|
173
|
+
return base64Encode(bin).replace(/\+/g, "-").replace(/\//g, "_").replace(/=+$/, "");
|
|
174
|
+
}
|
|
175
|
+
function base64Encode(binary) {
|
|
176
|
+
if (typeof globalThis.btoa === "function")
|
|
177
|
+
return globalThis.btoa(binary);
|
|
178
|
+
const chars = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/";
|
|
179
|
+
let out = "";
|
|
180
|
+
let i = 0;
|
|
181
|
+
while (i < binary.length) {
|
|
182
|
+
const a = binary.charCodeAt(i++);
|
|
183
|
+
const b = i < binary.length ? binary.charCodeAt(i++) : NaN;
|
|
184
|
+
const c = i < binary.length ? binary.charCodeAt(i++) : NaN;
|
|
185
|
+
const triplet = (a << 16) | ((Number.isNaN(b) ? 0 : b) << 8) | (Number.isNaN(c) ? 0 : c);
|
|
186
|
+
out += chars[(triplet >> 18) & 63];
|
|
187
|
+
out += chars[(triplet >> 12) & 63];
|
|
188
|
+
out += Number.isNaN(b) ? "=" : chars[(triplet >> 6) & 63];
|
|
189
|
+
out += Number.isNaN(c) ? "=" : chars[triplet & 63];
|
|
190
|
+
}
|
|
191
|
+
return out;
|
|
192
|
+
}
|
|
193
|
+
//# sourceMappingURL=carrier-commands.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"carrier-commands.js","sourceRoot":"","sources":["../src/carrier-commands.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,37 @@
|
|
|
1
|
+
import { type Scheduler, type VoiceAgentSession } from "@kuralle-syrinx/core";
|
|
2
|
+
import type { ManagedSocket } from "@kuralle-syrinx/ws";
|
|
3
|
+
import { type BackgroundAudioConfig } from "./background-audio.js";
|
|
4
|
+
import { createWorkersInboundSocket, type WorkersDurableObjectWebSocketContext } from "@kuralle-syrinx/ws/workers";
|
|
5
|
+
import type { SessionStore } from "./session-store.js";
|
|
6
|
+
import { type TelnyxCodec } from "./telnyx-codec.js";
|
|
7
|
+
export interface TelnyxEdgeWebSocketOptions {
|
|
8
|
+
readonly sessionStore: SessionStore;
|
|
9
|
+
readonly createSession: (request: Request) => VoiceAgentSession | Promise<VoiceAgentSession>;
|
|
10
|
+
readonly scheduler?: Scheduler;
|
|
11
|
+
/** Engine-side PCM rate (default 16000). Wire rate follows the negotiated codec. */
|
|
12
|
+
readonly engineSampleRateHz?: number;
|
|
13
|
+
/**
|
|
14
|
+
* Default bidirectional codec when `start.media_format` is absent (should match the
|
|
15
|
+
* `stream_bidirectional_codec` used when starting the Telnyx stream). @default "PCMU"
|
|
16
|
+
*/
|
|
17
|
+
readonly bidirectionalCodec?: TelnyxCodec;
|
|
18
|
+
/**
|
|
19
|
+
* Ambient/thinking bed: mixed (ducked) under assistant speech, and sent as
|
|
20
|
+
* comfort-noise frames between turns — pure digital silence on a phone line
|
|
21
|
+
* reads as "the call died". Thinking loop follows the G3 tool-call cues.
|
|
22
|
+
*/
|
|
23
|
+
readonly backgroundAudio?: BackgroundAudioConfig;
|
|
24
|
+
/** Cadence of idle comfort-noise frames (ms). @default 200 */
|
|
25
|
+
readonly backgroundIdleFrameMs?: number;
|
|
26
|
+
readonly resumeWindowMs?: number;
|
|
27
|
+
readonly keepAliveIntervalMs?: number;
|
|
28
|
+
readonly idleTimeoutMs?: number;
|
|
29
|
+
readonly startupTimeoutMs?: number;
|
|
30
|
+
}
|
|
31
|
+
export interface TelnyxEdgeWebSocketUpgrade {
|
|
32
|
+
readonly response: Response;
|
|
33
|
+
readonly controller: ReturnType<typeof createWorkersInboundSocket>["controller"];
|
|
34
|
+
}
|
|
35
|
+
export declare function createTelnyxEdgeWebSocketUpgrade(request: Request, options: TelnyxEdgeWebSocketOptions, ctx?: WorkersDurableObjectWebSocketContext): TelnyxEdgeWebSocketUpgrade;
|
|
36
|
+
export declare function runTelnyxEdgeWebSocketConnection(socket: ManagedSocket, request: Request, options: TelnyxEdgeWebSocketOptions): Promise<void>;
|
|
37
|
+
//# sourceMappingURL=edge-telnyx.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"edge-telnyx.d.ts","sourceRoot":"","sources":["../src/edge-telnyx.ts"],"names":[],"mappings":"AAcA,OAAO,EAGL,KAAK,SAAS,EACd,KAAK,iBAAiB,EAEvB,MAAM,sBAAsB,CAAC;AAO9B,OAAO,KAAK,EAAE,aAAa,EAAc,MAAM,oBAAoB,CAAC;AACpE,OAAO,EAGL,KAAK,qBAAqB,EAC3B,MAAM,uBAAuB,CAAC;AAC/B,OAAO,EACL,0BAA0B,EAC1B,KAAK,oCAAoC,EAC1C,MAAM,4BAA4B,CAAC;AACpC,OAAO,KAAK,EAAE,YAAY,EAAkB,MAAM,oBAAoB,CAAC;AACvE,OAAO,EAOL,KAAK,WAAW,EAGjB,MAAM,mBAAmB,CAAC;AAQ3B,MAAM,WAAW,0BAA0B;IACzC,QAAQ,CAAC,YAAY,EAAE,YAAY,CAAC;IACpC,QAAQ,CAAC,aAAa,EAAE,CAAC,OAAO,EAAE,OAAO,KAAK,iBAAiB,GAAG,OAAO,CAAC,iBAAiB,CAAC,CAAC;IAC7F,QAAQ,CAAC,SAAS,CAAC,EAAE,SAAS,CAAC;IAC/B,oFAAoF;IACpF,QAAQ,CAAC,kBAAkB,CAAC,EAAE,MAAM,CAAC;IACrC;;;OAGG;IACH,QAAQ,CAAC,kBAAkB,CAAC,EAAE,WAAW,CAAC;IAC1C;;;;OAIG;IACH,QAAQ,CAAC,eAAe,CAAC,EAAE,qBAAqB,CAAC;IACjD,8DAA8D;IAC9D,QAAQ,CAAC,qBAAqB,CAAC,EAAE,MAAM,CAAC;IACxC,QAAQ,CAAC,cAAc,CAAC,EAAE,MAAM,CAAC;IACjC,QAAQ,CAAC,mBAAmB,CAAC,EAAE,MAAM,CAAC;IACtC,QAAQ,CAAC,aAAa,CAAC,EAAE,MAAM,CAAC;IAChC,QAAQ,CAAC,gBAAgB,CAAC,EAAE,MAAM,CAAC;CACpC;AAED,MAAM,WAAW,0BAA0B;IACzC,QAAQ,CAAC,QAAQ,EAAE,QAAQ,CAAC;IAC5B,QAAQ,CAAC,UAAU,EAAE,UAAU,CAAC,OAAO,0BAA0B,CAAC,CAAC,YAAY,CAAC,CAAC;CAClF;AAED,wBAAgB,gCAAgC,CAC9C,OAAO,EAAE,OAAO,EAChB,OAAO,EAAE,0BAA0B,EACnC,GAAG,CAAC,EAAE,oCAAoC,GACzC,0BAA0B,CAI5B;AAED,wBAAsB,gCAAgC,CACpD,MAAM,EAAE,aAAa,EACrB,OAAO,EAAE,OAAO,EAChB,OAAO,EAAE,0BAA0B,GAClC,OAAO,CAAC,IAAI,CAAC,CA0Qf"}
|
|
@@ -0,0 +1,320 @@
|
|
|
1
|
+
// SPDX-License-Identifier: MIT
|
|
2
|
+
//
|
|
3
|
+
// Telnyx Media Streaming ingress for the Workers edge: bridges Telnyx's WebSocket
|
|
4
|
+
// protocol (base64 RTP-in-JSON, PCMU/PCMA/G722/L16) to a VoiceAgentSession, mirroring
|
|
5
|
+
// the session-lease/heartbeat pattern of edge-twilio.ts. Provider-agnostic — the bridge
|
|
6
|
+
// only speaks core packets (user.audio_received / tts.audio / interrupt.detected).
|
|
7
|
+
//
|
|
8
|
+
// Codec transcoding is shared with the Node host via telnyx-codec.ts (Workers-safe).
|
|
9
|
+
// Live trunk negotiation (streaming_start → /telnyx) is carrier-gated / unit-tested only.
|
|
10
|
+
//
|
|
11
|
+
// Barge-in: interrupt.detected → Telnyx `clear` event. Playout-clock caveat: like the
|
|
12
|
+
// browser/Twilio edge paths, turn-taking uses the estimate fallback (no paced transport
|
|
13
|
+
// progress events on the edge yet).
|
|
14
|
+
import { Route, TimerScheduler, } from "@kuralle-syrinx/core";
|
|
15
|
+
import { pcm16BytesToSamples, pcm16SamplesToBytes, resamplePcm16Streaming, } from "@kuralle-syrinx/core/audio";
|
|
16
|
+
import { BackgroundAudioMixer, wireBackgroundAudio, } from "./background-audio.js";
|
|
17
|
+
import { createWorkersInboundSocket, } from "@kuralle-syrinx/ws/workers";
|
|
18
|
+
import { createTelnyxG722State, decodeTelnyxInboundPayload, defaultTelnyxContextId, encodeTelnyxOutboundPayload, validateTelnyxStart, wireSampleRateForCodec, } from "./telnyx-codec.js";
|
|
19
|
+
const DEFAULT_RESUME_WINDOW_MS = 15_000;
|
|
20
|
+
const DEFAULT_KEEP_ALIVE_INTERVAL_MS = 15_000;
|
|
21
|
+
const DEFAULT_IDLE_TIMEOUT_MS = 60_000;
|
|
22
|
+
const DEFAULT_STARTUP_TIMEOUT_MS = 15_000;
|
|
23
|
+
const KEEP_ALIVE_KEY = "voice.edge.telnyx.keep_alive";
|
|
24
|
+
export function createTelnyxEdgeWebSocketUpgrade(request, options, ctx) {
|
|
25
|
+
const inbound = createWorkersInboundSocket(ctx);
|
|
26
|
+
void runTelnyxEdgeWebSocketConnection(inbound.socket, request, options);
|
|
27
|
+
return { response: inbound.response, controller: inbound.controller };
|
|
28
|
+
}
|
|
29
|
+
export async function runTelnyxEdgeWebSocketConnection(socket, request, options) {
|
|
30
|
+
const scheduler = options.scheduler ?? new TimerScheduler();
|
|
31
|
+
const engineRate = options.engineSampleRateHz ?? 16_000;
|
|
32
|
+
const defaultCodec = options.bidirectionalCodec ?? "PCMU";
|
|
33
|
+
const resumeWindowMs = options.resumeWindowMs ?? DEFAULT_RESUME_WINDOW_MS;
|
|
34
|
+
const keepAliveIntervalMs = options.keepAliveIntervalMs ?? DEFAULT_KEEP_ALIVE_INTERVAL_MS;
|
|
35
|
+
const idleTimeoutMs = options.idleTimeoutMs ?? DEFAULT_IDLE_TIMEOUT_MS;
|
|
36
|
+
const startupTimeoutMs = options.startupTimeoutMs ?? DEFAULT_STARTUP_TIMEOUT_MS;
|
|
37
|
+
const uplinkResamplers = new Map();
|
|
38
|
+
const downlinkResamplers = new Map();
|
|
39
|
+
const disposers = [];
|
|
40
|
+
let session = null;
|
|
41
|
+
let managed = null;
|
|
42
|
+
let pendingLease = null;
|
|
43
|
+
let sessionId = "";
|
|
44
|
+
let streamId = "";
|
|
45
|
+
let contextId = "";
|
|
46
|
+
let contextBase = "";
|
|
47
|
+
let turnCounter = 0;
|
|
48
|
+
let closed = false;
|
|
49
|
+
let stopped = false;
|
|
50
|
+
let lastClientMessageMs = Date.now();
|
|
51
|
+
let wireCodec = defaultCodec;
|
|
52
|
+
let wireSampleRateHz = wireSampleRateForCodec(defaultCodec);
|
|
53
|
+
let g722 = createTelnyxG722State(defaultCodec);
|
|
54
|
+
let started = false;
|
|
55
|
+
const sendTelnyxJson = (value) => {
|
|
56
|
+
if (!socket.isOpen)
|
|
57
|
+
return;
|
|
58
|
+
socket.send(JSON.stringify(value));
|
|
59
|
+
};
|
|
60
|
+
const encodeWirePayload = (samplesAtWireRate) => encodeTelnyxOutboundPayload(samplesAtWireRate, wireCodec, g722);
|
|
61
|
+
const cleanup = () => {
|
|
62
|
+
if (closed)
|
|
63
|
+
return;
|
|
64
|
+
closed = true;
|
|
65
|
+
scheduler.cancel(KEEP_ALIVE_KEY);
|
|
66
|
+
scheduler.cancel("voice.edge.telnyx.startup");
|
|
67
|
+
for (const dispose of disposers.splice(0))
|
|
68
|
+
dispose();
|
|
69
|
+
if (managed && sessionId) {
|
|
70
|
+
// Decrement the connection count BEFORE releasing (R1) — otherwise release
|
|
71
|
+
// early-returns while connectionCount > 0 and session.close() never runs, so
|
|
72
|
+
// Deepgram/TTS provider sockets + the reasoner leak until DO eviction. A
|
|
73
|
+
// caller-hangup (`stopped`) releases immediately (retain 0); a transient drop
|
|
74
|
+
// keeps the session warm for the resume window.
|
|
75
|
+
managed.connectionCount = Math.max(0, managed.connectionCount - 1);
|
|
76
|
+
void options.sessionStore.release(sessionId, stopped ? 0 : resumeWindowMs);
|
|
77
|
+
}
|
|
78
|
+
else if (pendingLease && sessionId) {
|
|
79
|
+
// Closed before the lease was adopted (e.g. startup-timeout race): tear the
|
|
80
|
+
// in-flight session down when it resolves so it is never orphaned in the store.
|
|
81
|
+
const id = sessionId;
|
|
82
|
+
void pendingLease
|
|
83
|
+
.then((leased) => {
|
|
84
|
+
leased.managed.connectionCount = Math.max(0, leased.managed.connectionCount - 1);
|
|
85
|
+
return options.sessionStore.release(id, 0);
|
|
86
|
+
})
|
|
87
|
+
.catch(() => undefined);
|
|
88
|
+
}
|
|
89
|
+
};
|
|
90
|
+
socket.onClose(() => {
|
|
91
|
+
cleanup();
|
|
92
|
+
});
|
|
93
|
+
socket.onError(() => {
|
|
94
|
+
cleanup();
|
|
95
|
+
});
|
|
96
|
+
// Telnyx sends connected/start (and media) immediately after the upgrade —
|
|
97
|
+
// before the session lease (provider sockets, kuralle init) resolves. Buffer
|
|
98
|
+
// until the handler is live or the start event is lost and the whole call
|
|
99
|
+
// streams into the void.
|
|
100
|
+
const pendingMessages = [];
|
|
101
|
+
let pendingBytes = 0;
|
|
102
|
+
let liveHandler = null;
|
|
103
|
+
socket.onMessage((data, isBinary) => {
|
|
104
|
+
lastClientMessageMs = Date.now();
|
|
105
|
+
if (liveHandler) {
|
|
106
|
+
liveHandler(data, isBinary);
|
|
107
|
+
return;
|
|
108
|
+
}
|
|
109
|
+
const byteLength = typeof data === "string" ? data.length : data.byteLength;
|
|
110
|
+
if (pendingBytes + byteLength > 2 * 1024 * 1024)
|
|
111
|
+
return; // cap startup buffering
|
|
112
|
+
pendingBytes += byteLength;
|
|
113
|
+
pendingMessages.push({ data, isBinary });
|
|
114
|
+
});
|
|
115
|
+
try {
|
|
116
|
+
const url = new URL(request.url);
|
|
117
|
+
sessionId = url.searchParams.get("sessionId")?.trim() || crypto.randomUUID();
|
|
118
|
+
const startupTimer = new Promise((_, reject) => {
|
|
119
|
+
scheduler.schedule("voice.edge.telnyx.startup", startupTimeoutMs, () => {
|
|
120
|
+
reject(new Error("telnyx session startup timeout"));
|
|
121
|
+
});
|
|
122
|
+
});
|
|
123
|
+
pendingLease = options.sessionStore.lease(sessionId, async () => {
|
|
124
|
+
const sess = await options.createSession(request);
|
|
125
|
+
await sess.start();
|
|
126
|
+
return {
|
|
127
|
+
id: sessionId,
|
|
128
|
+
session: sess,
|
|
129
|
+
currentContextId: "",
|
|
130
|
+
contextSampleRates: new Map(),
|
|
131
|
+
inputSequence: { lastSequence: null },
|
|
132
|
+
turnMetricsTurns: new Map(),
|
|
133
|
+
closeTimer: null,
|
|
134
|
+
connectionCount: 1,
|
|
135
|
+
};
|
|
136
|
+
});
|
|
137
|
+
const leased = await Promise.race([pendingLease, startupTimer]);
|
|
138
|
+
pendingLease = null;
|
|
139
|
+
scheduler.cancel("voice.edge.telnyx.startup");
|
|
140
|
+
managed = leased.managed;
|
|
141
|
+
session = managed.session;
|
|
142
|
+
if (closed) {
|
|
143
|
+
// The socket closed while the session was starting up — tear the just-started
|
|
144
|
+
// session down instead of orphaning it (decrement so release actually closes it).
|
|
145
|
+
managed.connectionCount = Math.max(0, managed.connectionCount - 1);
|
|
146
|
+
await options.sessionStore.release(sessionId, 0);
|
|
147
|
+
return;
|
|
148
|
+
}
|
|
149
|
+
// Background bed: ducked under speech in the tts.audio handler below, and
|
|
150
|
+
// sent as idle comfort-noise frames between turns — a phone line carrying
|
|
151
|
+
// pure digital silence reads as "the call died". Telnyx's `clear` on
|
|
152
|
+
// barge-in drops any queued bed audio; the ticker refills on the next tick.
|
|
153
|
+
const backgroundAudio = options.backgroundAudio
|
|
154
|
+
? new BackgroundAudioMixer(options.backgroundAudio)
|
|
155
|
+
: null;
|
|
156
|
+
if (backgroundAudio) {
|
|
157
|
+
wireBackgroundAudio(session, backgroundAudio);
|
|
158
|
+
const idleFrameMs = options.backgroundIdleFrameMs ?? 200;
|
|
159
|
+
const idleTimer = setInterval(() => {
|
|
160
|
+
if (!streamId || !started || stopped || closed || !socket.isOpen)
|
|
161
|
+
return;
|
|
162
|
+
const frame = backgroundAudio.idleFrame(idleFrameMs, wireSampleRateHz);
|
|
163
|
+
if (!frame)
|
|
164
|
+
return;
|
|
165
|
+
const samples = pcm16BytesToSamples(frame);
|
|
166
|
+
const payload = encodeWirePayload(samples);
|
|
167
|
+
sendTelnyxJson({ event: "media", media: { payload: bytesToBase64(payload) } });
|
|
168
|
+
}, idleFrameMs);
|
|
169
|
+
disposers.push(() => clearInterval(idleTimer));
|
|
170
|
+
}
|
|
171
|
+
// Downlink: engine PCM → wire codec media frames; barge-in → clear.
|
|
172
|
+
disposers.push(session.bus.on("tts.audio", (pkt) => {
|
|
173
|
+
if (!streamId || !started)
|
|
174
|
+
return;
|
|
175
|
+
const audio = pkt;
|
|
176
|
+
const sourceRate = audio.sampleRateHz ?? engineRate;
|
|
177
|
+
const wireAudio = backgroundAudio ? backgroundAudio.mix(audio.audio, sourceRate) : audio.audio;
|
|
178
|
+
const samples = pcm16BytesToSamples(wireAudio);
|
|
179
|
+
const resampled = resamplePcm16Streaming(downlinkResamplers, samples, sourceRate, wireSampleRateHz);
|
|
180
|
+
const payload = encodeWirePayload(resampled);
|
|
181
|
+
sendTelnyxJson({
|
|
182
|
+
event: "media",
|
|
183
|
+
media: { payload: bytesToBase64(payload) },
|
|
184
|
+
});
|
|
185
|
+
}), session.bus.on("interrupt.detected", () => {
|
|
186
|
+
if (!streamId || !started)
|
|
187
|
+
return;
|
|
188
|
+
sendTelnyxJson({ event: "clear" });
|
|
189
|
+
}),
|
|
190
|
+
// The phone mic streams continuously on one call, but the engine finishes a
|
|
191
|
+
// turn per contextId — rotate on turn_complete exactly like the browser
|
|
192
|
+
// client does, or only the first utterance is ever heard (the STT plugin
|
|
193
|
+
// drops transcripts for already-finalized contexts).
|
|
194
|
+
session.bus.on("eos.turn_complete", () => {
|
|
195
|
+
if (!contextBase)
|
|
196
|
+
return;
|
|
197
|
+
turnCounter += 1;
|
|
198
|
+
contextId = `${contextBase}-t${String(turnCounter)}`;
|
|
199
|
+
}));
|
|
200
|
+
if (keepAliveIntervalMs > 0) {
|
|
201
|
+
const heartbeat = () => {
|
|
202
|
+
if (closed)
|
|
203
|
+
return;
|
|
204
|
+
if (idleTimeoutMs > 0 && Date.now() - lastClientMessageMs > idleTimeoutMs) {
|
|
205
|
+
socket.dispose();
|
|
206
|
+
return;
|
|
207
|
+
}
|
|
208
|
+
scheduler.schedule(KEEP_ALIVE_KEY, keepAliveIntervalMs, heartbeat);
|
|
209
|
+
};
|
|
210
|
+
scheduler.schedule(KEEP_ALIVE_KEY, keepAliveIntervalMs, heartbeat);
|
|
211
|
+
}
|
|
212
|
+
const handleMessage = (data, isBinary) => {
|
|
213
|
+
if (isBinary || closed || !session)
|
|
214
|
+
return;
|
|
215
|
+
let message;
|
|
216
|
+
try {
|
|
217
|
+
message = JSON.parse(socketDataToText(data));
|
|
218
|
+
}
|
|
219
|
+
catch {
|
|
220
|
+
return;
|
|
221
|
+
}
|
|
222
|
+
const event = message["event"];
|
|
223
|
+
if (event === "connected" || event === "mark" || event === "dtmf")
|
|
224
|
+
return;
|
|
225
|
+
if (event === "start") {
|
|
226
|
+
const start = (message["start"] ?? {});
|
|
227
|
+
streamId =
|
|
228
|
+
(typeof message["stream_id"] === "string" && message["stream_id"]) ||
|
|
229
|
+
(typeof start.stream_id === "string" && start.stream_id) ||
|
|
230
|
+
"";
|
|
231
|
+
try {
|
|
232
|
+
if (start.media_format) {
|
|
233
|
+
const format = validateTelnyxStart(start);
|
|
234
|
+
wireCodec = format.codec;
|
|
235
|
+
wireSampleRateHz = format.sampleRateHz;
|
|
236
|
+
g722 = createTelnyxG722State(wireCodec);
|
|
237
|
+
}
|
|
238
|
+
else {
|
|
239
|
+
wireCodec = defaultCodec;
|
|
240
|
+
wireSampleRateHz = wireSampleRateForCodec(defaultCodec);
|
|
241
|
+
g722 = createTelnyxG722State(defaultCodec);
|
|
242
|
+
}
|
|
243
|
+
}
|
|
244
|
+
catch {
|
|
245
|
+
// Malformed start: keep defaults so the call can still proceed with
|
|
246
|
+
// the configured bidirectional codec (unit path); live trunks always
|
|
247
|
+
// send media_format.
|
|
248
|
+
}
|
|
249
|
+
contextBase = defaultTelnyxContextId({
|
|
250
|
+
...start,
|
|
251
|
+
stream_id: streamId || start.stream_id,
|
|
252
|
+
});
|
|
253
|
+
contextId = contextBase;
|
|
254
|
+
started = true;
|
|
255
|
+
return;
|
|
256
|
+
}
|
|
257
|
+
if (event === "media") {
|
|
258
|
+
const media = (message["media"] ?? {});
|
|
259
|
+
const payload = typeof media["payload"] === "string" ? media["payload"] : "";
|
|
260
|
+
if (!payload || !contextId || !started)
|
|
261
|
+
return;
|
|
262
|
+
const encoded = base64ToBytes(payload);
|
|
263
|
+
const pcmWire = decodeTelnyxInboundPayload(encoded, wireCodec, g722);
|
|
264
|
+
const pcmEngine = resamplePcm16Streaming(uplinkResamplers, pcmWire, wireSampleRateHz, engineRate);
|
|
265
|
+
session.bus.push(Route.Main, {
|
|
266
|
+
kind: "user.audio_received",
|
|
267
|
+
contextId,
|
|
268
|
+
timestampMs: Date.now(),
|
|
269
|
+
audio: pcm16SamplesToBytes(pcmEngine),
|
|
270
|
+
sampleRateHz: engineRate,
|
|
271
|
+
});
|
|
272
|
+
return;
|
|
273
|
+
}
|
|
274
|
+
if (event === "stop") {
|
|
275
|
+
stopped = true;
|
|
276
|
+
started = false;
|
|
277
|
+
socket.dispose();
|
|
278
|
+
}
|
|
279
|
+
};
|
|
280
|
+
liveHandler = handleMessage;
|
|
281
|
+
for (const queued of pendingMessages.splice(0)) {
|
|
282
|
+
handleMessage(queued.data, queued.isBinary);
|
|
283
|
+
}
|
|
284
|
+
}
|
|
285
|
+
catch (err) {
|
|
286
|
+
sendTelnyxJson({
|
|
287
|
+
event: "error",
|
|
288
|
+
payload: {
|
|
289
|
+
code: 100003,
|
|
290
|
+
title: "syrinx_transport_error",
|
|
291
|
+
detail: err instanceof Error ? err.message : String(err),
|
|
292
|
+
},
|
|
293
|
+
});
|
|
294
|
+
socket.dispose();
|
|
295
|
+
cleanup();
|
|
296
|
+
}
|
|
297
|
+
}
|
|
298
|
+
function socketDataToText(data) {
|
|
299
|
+
if (typeof data === "string")
|
|
300
|
+
return data;
|
|
301
|
+
if (data instanceof ArrayBuffer)
|
|
302
|
+
return new TextDecoder().decode(new Uint8Array(data));
|
|
303
|
+
return new TextDecoder().decode(data);
|
|
304
|
+
}
|
|
305
|
+
function base64ToBytes(value) {
|
|
306
|
+
const binary = atob(value);
|
|
307
|
+
const bytes = new Uint8Array(binary.length);
|
|
308
|
+
for (let i = 0; i < binary.length; i += 1)
|
|
309
|
+
bytes[i] = binary.charCodeAt(i);
|
|
310
|
+
return bytes;
|
|
311
|
+
}
|
|
312
|
+
function bytesToBase64(bytes) {
|
|
313
|
+
let binary = "";
|
|
314
|
+
const chunkSize = 0x8000;
|
|
315
|
+
for (let offset = 0; offset < bytes.length; offset += chunkSize) {
|
|
316
|
+
binary += String.fromCharCode(...bytes.subarray(offset, offset + chunkSize));
|
|
317
|
+
}
|
|
318
|
+
return btoa(binary);
|
|
319
|
+
}
|
|
320
|
+
//# sourceMappingURL=edge-telnyx.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"edge-telnyx.js","sourceRoot":"","sources":["../src/edge-telnyx.ts"],"names":[],"mappings":"AAAA,+BAA+B;AAC/B,EAAE;AACF,kFAAkF;AAClF,sFAAsF;AACtF,wFAAwF;AACxF,mFAAmF;AACnF,EAAE;AACF,qFAAqF;AACrF,0FAA0F;AAC1F,EAAE;AACF,sFAAsF;AACtF,wFAAwF;AACxF,oCAAoC;AAEpC,OAAO,EACL,KAAK,EACL,cAAc,GAIf,MAAM,sBAAsB,CAAC;AAC9B,OAAO,EACL,mBAAmB,EACnB,mBAAmB,EACnB,sBAAsB,GAEvB,MAAM,4BAA4B,CAAC;AAEpC,OAAO,EACL,oBAAoB,EACpB,mBAAmB,GAEpB,MAAM,uBAAuB,CAAC;AAC/B,OAAO,EACL,0BAA0B,GAE3B,MAAM,4BAA4B,CAAC;AAEpC,OAAO,EACL,qBAAqB,EACrB,0BAA0B,EAC1B,sBAAsB,EACtB,2BAA2B,EAC3B,mBAAmB,EACnB,sBAAsB,GAIvB,MAAM,mBAAmB,CAAC;AAE3B,MAAM,wBAAwB,GAAG,MAAM,CAAC;AACxC,MAAM,8BAA8B,GAAG,MAAM,CAAC;AAC9C,MAAM,uBAAuB,GAAG,MAAM,CAAC;AACvC,MAAM,0BAA0B,GAAG,MAAM,CAAC;AAC1C,MAAM,cAAc,GAAG,8BAA8B,CAAC;AAgCtD,MAAM,UAAU,gCAAgC,CAC9C,OAAgB,EAChB,OAAmC,EACnC,GAA0C;IAE1C,MAAM,OAAO,GAAG,0BAA0B,CAAC,GAAG,CAAC,CAAC;IAChD,KAAK,gCAAgC,CAAC,OAAO,CAAC,MAAM,EAAE,OAAO,EAAE,OAAO,CAAC,CAAC;IACxE,OAAO,EAAE,QAAQ,EAAE,OAAO,CAAC,QAAQ,EAAE,UAAU,EAAE,OAAO,CAAC,UAAU,EAAE,CAAC;AACxE,CAAC;AAED,MAAM,CAAC,KAAK,UAAU,gCAAgC,CACpD,MAAqB,EACrB,OAAgB,EAChB,OAAmC;IAEnC,MAAM,SAAS,GAAG,OAAO,CAAC,SAAS,IAAI,IAAI,cAAc,EAAE,CAAC;IAC5D,MAAM,UAAU,GAAG,OAAO,CAAC,kBAAkB,IAAI,MAAM,CAAC;IACxD,MAAM,YAAY,GAAgB,OAAO,CAAC,kBAAkB,IAAI,MAAM,CAAC;IACvE,MAAM,cAAc,GAAG,OAAO,CAAC,cAAc,IAAI,wBAAwB,CAAC;IAC1E,MAAM,mBAAmB,GAAG,OAAO,CAAC,mBAAmB,IAAI,8BAA8B,CAAC;IAC1F,MAAM,aAAa,GAAG,OAAO,CAAC,aAAa,IAAI,uBAAuB,CAAC;IACvE,MAAM,gBAAgB,GAAG,OAAO,CAAC,gBAAgB,IAAI,0BAA0B,CAAC;IAEhF,MAAM,gBAAgB,GAAG,IAAI,GAAG,EAAmC,CAAC;IACpE,MAAM,kBAAkB,GAAG,IAAI,GAAG,EAAmC,CAAC;IACtE,MAAM,SAAS,GAAsB,EAAE,CAAC;IACxC,IAAI,OAAO,GAA6B,IAAI,CAAC;IAC7C,IAAI,OAAO,GAA0B,IAAI,CAAC;IAC1C,IAAI,YAAY,GAAgD,IAAI,CAAC;IACrE,IAAI,SAAS,GAAG,EAAE,CAAC;IACnB,IAAI,QAAQ,GAAG,EAAE,CAAC;IAClB,IAAI,SAAS,GAAG,EAAE,CAAC;IACnB,IAAI,WAAW,GAAG,EAAE,CAAC;IACrB,IAAI,WAAW,GAAG,CAAC,CAAC;IACpB,IAAI,MAAM,GAAG,KAAK,CAAC;IACnB,IAAI,OAAO,GAAG,KAAK,CAAC;IACpB,IAAI,mBAAmB,GAAG,IAAI,CAAC,GAAG,EAAE,CAAC;IACrC,IAAI,SAAS,GAAgB,YAAY,CAAC;IAC1C,IAAI,gBAAgB,GAAG,sBAAsB,CAAC,YAAY,CAAC,CAAC;IAC5D,IAAI,IAAI,GAAoB,qBAAqB,CAAC,YAAY,CAAC,CAAC;IAChE,IAAI,OAAO,GAAG,KAAK,CAAC;IAEpB,MAAM,cAAc,GAAG,CAAC,KAAc,EAAQ,EAAE;QAC9C,IAAI,CAAC,MAAM,CAAC,MAAM;YAAE,OAAO;QAC3B,MAAM,CAAC,IAAI,CAAC,IAAI,CAAC,SAAS,CAAC,KAAK,CAAC,CAAC,CAAC;IACrC,CAAC,CAAC;IAEF,MAAM,iBAAiB,GAAG,CAAC,iBAA6B,EAAc,EAAE,CACtE,2BAA2B,CAAC,iBAAiB,EAAE,SAAS,EAAE,IAAI,CAAC,CAAC;IAElE,MAAM,OAAO,GAAG,GAAS,EAAE;QACzB,IAAI,MAAM;YAAE,OAAO;QACnB,MAAM,GAAG,IAAI,CAAC;QACd,SAAS,CAAC,MAAM,CAAC,cAAc,CAAC,CAAC;QACjC,SAAS,CAAC,MAAM,CAAC,2BAA2B,CAAC,CAAC;QAC9C,KAAK,MAAM,OAAO,IAAI,SAAS,CAAC,MAAM,CAAC,CAAC,CAAC;YAAE,OAAO,EAAE,CAAC;QACrD,IAAI,OAAO,IAAI,SAAS,EAAE,CAAC;YACzB,2EAA2E;YAC3E,6EAA6E;YAC7E,yEAAyE;YACzE,8EAA8E;YAC9E,gDAAgD;YAChD,OAAO,CAAC,eAAe,GAAG,IAAI,CAAC,GAAG,CAAC,CAAC,EAAE,OAAO,CAAC,eAAe,GAAG,CAAC,CAAC,CAAC;YACnE,KAAK,OAAO,CAAC,YAAY,CAAC,OAAO,CAAC,SAAS,EAAE,OAAO,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,cAAc,CAAC,CAAC;QAC7E,CAAC;aAAM,IAAI,YAAY,IAAI,SAAS,EAAE,CAAC;YACrC,4EAA4E;YAC5E,gFAAgF;YAChF,MAAM,EAAE,GAAG,SAAS,CAAC;YACrB,KAAK,YAAY;iBACd,IAAI,CAAC,CAAC,MAAM,EAAE,EAAE;gBACf,MAAM,CAAC,OAAO,CAAC,eAAe,GAAG,IAAI,CAAC,GAAG,CAAC,CAAC,EAAE,MAAM,CAAC,OAAO,CAAC,eAAe,GAAG,CAAC,CAAC,CAAC;gBACjF,OAAO,OAAO,CAAC,YAAY,CAAC,OAAO,CAAC,EAAE,EAAE,CAAC,CAAC,CAAC;YAC7C,CAAC,CAAC;iBACD,KAAK,CAAC,GAAG,EAAE,CAAC,SAAS,CAAC,CAAC;QAC5B,CAAC;IACH,CAAC,CAAC;IAEF,MAAM,CAAC,OAAO,CAAC,GAAG,EAAE;QAClB,OAAO,EAAE,CAAC;IACZ,CAAC,CAAC,CAAC;IACH,MAAM,CAAC,OAAO,CAAC,GAAG,EAAE;QAClB,OAAO,EAAE,CAAC;IACZ,CAAC,CAAC,CAAC;IAEH,2EAA2E;IAC3E,6EAA6E;IAC7E,0EAA0E;IAC1E,yBAAyB;IACzB,MAAM,eAAe,GAAmD,EAAE,CAAC;IAC3E,IAAI,YAAY,GAAG,CAAC,CAAC;IACrB,IAAI,WAAW,GAA2D,IAAI,CAAC;IAC/E,MAAM,CAAC,SAAS,CAAC,CAAC,IAAgB,EAAE,QAAiB,EAAE,EAAE;QACvD,mBAAmB,GAAG,IAAI,CAAC,GAAG,EAAE,CAAC;QACjC,IAAI,WAAW,EAAE,CAAC;YAChB,WAAW,CAAC,IAAI,EAAE,QAAQ,CAAC,CAAC;YAC5B,OAAO;QACT,CAAC;QACD,MAAM,UAAU,GAAG,OAAO,IAAI,KAAK,QAAQ,CAAC,CAAC,CAAC,IAAI,CAAC,MAAM,CAAC,CAAC,CAAE,IAAiC,CAAC,UAAU,CAAC;QAC1G,IAAI,YAAY,GAAG,UAAU,GAAG,CAAC,GAAG,IAAI,GAAG,IAAI;YAAE,OAAO,CAAC,wBAAwB;QACjF,YAAY,IAAI,UAAU,CAAC;QAC3B,eAAe,CAAC,IAAI,CAAC,EAAE,IAAI,EAAE,QAAQ,EAAE,CAAC,CAAC;IAC3C,CAAC,CAAC,CAAC;IAEH,IAAI,CAAC;QACH,MAAM,GAAG,GAAG,IAAI,GAAG,CAAC,OAAO,CAAC,GAAG,CAAC,CAAC;QACjC,SAAS,GAAG,GAAG,CAAC,YAAY,CAAC,GAAG,CAAC,WAAW,CAAC,EAAE,IAAI,EAAE,IAAI,MAAM,CAAC,UAAU,EAAE,CAAC;QAE7E,MAAM,YAAY,GAAG,IAAI,OAAO,CAAQ,CAAC,CAAC,EAAE,MAAM,EAAE,EAAE;YACpD,SAAS,CAAC,QAAQ,CAAC,2BAA2B,EAAE,gBAAgB,EAAE,GAAG,EAAE;gBACrE,MAAM,CAAC,IAAI,KAAK,CAAC,gCAAgC,CAAC,CAAC,CAAC;YACtD,CAAC,CAAC,CAAC;QACL,CAAC,CAAC,CAAC;QACH,YAAY,GAAG,OAAO,CAAC,YAAY,CAAC,KAAK,CAAC,SAAS,EAAE,KAAK,IAAI,EAAE;YAC9D,MAAM,IAAI,GAAG,MAAM,OAAO,CAAC,aAAa,CAAC,OAAO,CAAC,CAAC;YAClD,MAAM,IAAI,CAAC,KAAK,EAAE,CAAC;YACnB,OAAO;gBACL,EAAE,EAAE,SAAS;gBACb,OAAO,EAAE,IAAI;gBACb,gBAAgB,EAAE,EAAE;gBACpB,kBAAkB,EAAE,IAAI,GAAG,EAAE;gBAC7B,aAAa,EAAE,EAAE,YAAY,EAAE,IAAI,EAAE;gBACrC,gBAAgB,EAAE,IAAI,GAAG,EAAE;gBAC3B,UAAU,EAAE,IAAI;gBAChB,eAAe,EAAE,CAAC;aACnB,CAAC;QACJ,CAAC,CAAC,CAAC;QACH,MAAM,MAAM,GAAG,MAAM,OAAO,CAAC,IAAI,CAAC,CAAC,YAAY,EAAE,YAAY,CAAC,CAAC,CAAC;QAChE,YAAY,GAAG,IAAI,CAAC;QACpB,SAAS,CAAC,MAAM,CAAC,2BAA2B,CAAC,CAAC;QAC9C,OAAO,GAAG,MAAM,CAAC,OAAO,CAAC;QACzB,OAAO,GAAG,OAAO,CAAC,OAAO,CAAC;QAC1B,IAAI,MAAM,EAAE,CAAC;YACX,8EAA8E;YAC9E,kFAAkF;YAClF,OAAO,CAAC,eAAe,GAAG,IAAI,CAAC,GAAG,CAAC,CAAC,EAAE,OAAO,CAAC,eAAe,GAAG,CAAC,CAAC,CAAC;YACnE,MAAM,OAAO,CAAC,YAAY,CAAC,OAAO,CAAC,SAAS,EAAE,CAAC,CAAC,CAAC;YACjD,OAAO;QACT,CAAC;QAED,0EAA0E;QAC1E,0EAA0E;QAC1E,qEAAqE;QACrE,4EAA4E;QAC5E,MAAM,eAAe,GAAG,OAAO,CAAC,eAAe;YAC7C,CAAC,CAAC,IAAI,oBAAoB,CAAC,OAAO,CAAC,eAAe,CAAC;YACnD,CAAC,CAAC,IAAI,CAAC;QACT,IAAI,eAAe,EAAE,CAAC;YACpB,mBAAmB,CAAC,OAAO,EAAE,eAAe,CAAC,CAAC;YAC9C,MAAM,WAAW,GAAG,OAAO,CAAC,qBAAqB,IAAI,GAAG,CAAC;YACzD,MAAM,SAAS,GAAG,WAAW,CAAC,GAAG,EAAE;gBACjC,IAAI,CAAC,QAAQ,IAAI,CAAC,OAAO,IAAI,OAAO,IAAI,MAAM,IAAI,CAAC,MAAM,CAAC,MAAM;oBAAE,OAAO;gBACzE,MAAM,KAAK,GAAG,eAAe,CAAC,SAAS,CAAC,WAAW,EAAE,gBAAgB,CAAC,CAAC;gBACvE,IAAI,CAAC,KAAK;oBAAE,OAAO;gBACnB,MAAM,OAAO,GAAG,mBAAmB,CAAC,KAAK,CAAC,CAAC;gBAC3C,MAAM,OAAO,GAAG,iBAAiB,CAAC,OAAO,CAAC,CAAC;gBAC3C,cAAc,CAAC,EAAE,KAAK,EAAE,OAAO,EAAE,KAAK,EAAE,EAAE,OAAO,EAAE,aAAa,CAAC,OAAO,CAAC,EAAE,EAAE,CAAC,CAAC;YACjF,CAAC,EAAE,WAAW,CAAC,CAAC;YAChB,SAAS,CAAC,IAAI,CAAC,GAAG,EAAE,CAAC,aAAa,CAAC,SAAS,CAAC,CAAC,CAAC;QACjD,CAAC;QAED,oEAAoE;QACpE,SAAS,CAAC,IAAI,CACZ,OAAO,CAAC,GAAG,CAAC,EAAE,CAAC,WAAW,EAAE,CAAC,GAAG,EAAE,EAAE;YAClC,IAAI,CAAC,QAAQ,IAAI,CAAC,OAAO;gBAAE,OAAO;YAClC,MAAM,KAAK,GAAG,GAA8B,CAAC;YAC7C,MAAM,UAAU,GAAG,KAAK,CAAC,YAAY,IAAI,UAAU,CAAC;YACpD,MAAM,SAAS,GAAG,eAAe,CAAC,CAAC,CAAC,eAAe,CAAC,GAAG,CAAC,KAAK,CAAC,KAAK,EAAE,UAAU,CAAC,CAAC,CAAC,CAAC,KAAK,CAAC,KAAK,CAAC;YAC/F,MAAM,OAAO,GAAG,mBAAmB,CAAC,SAAS,CAAC,CAAC;YAC/C,MAAM,SAAS,GAAG,sBAAsB,CAAC,kBAAkB,EAAE,OAAO,EAAE,UAAU,EAAE,gBAAgB,CAAC,CAAC;YACpG,MAAM,OAAO,GAAG,iBAAiB,CAAC,SAAS,CAAC,CAAC;YAC7C,cAAc,CAAC;gBACb,KAAK,EAAE,OAAO;gBACd,KAAK,EAAE,EAAE,OAAO,EAAE,aAAa,CAAC,OAAO,CAAC,EAAE;aAC3C,CAAC,CAAC;QACL,CAAC,CAAC,EACF,OAAO,CAAC,GAAG,CAAC,EAAE,CAAC,oBAAoB,EAAE,GAAG,EAAE;YACxC,IAAI,CAAC,QAAQ,IAAI,CAAC,OAAO;gBAAE,OAAO;YAClC,cAAc,CAAC,EAAE,KAAK,EAAE,OAAO,EAAE,CAAC,CAAC;QACrC,CAAC,CAAC;QACF,4EAA4E;QAC5E,wEAAwE;QACxE,yEAAyE;QACzE,qDAAqD;QACrD,OAAO,CAAC,GAAG,CAAC,EAAE,CAAC,mBAAmB,EAAE,GAAG,EAAE;YACvC,IAAI,CAAC,WAAW;gBAAE,OAAO;YACzB,WAAW,IAAI,CAAC,CAAC;YACjB,SAAS,GAAG,GAAG,WAAW,KAAK,MAAM,CAAC,WAAW,CAAC,EAAE,CAAC;QACvD,CAAC,CAAC,CACH,CAAC;QAEF,IAAI,mBAAmB,GAAG,CAAC,EAAE,CAAC;YAC5B,MAAM,SAAS,GAAG,GAAS,EAAE;gBAC3B,IAAI,MAAM;oBAAE,OAAO;gBACnB,IAAI,aAAa,GAAG,CAAC,IAAI,IAAI,CAAC,GAAG,EAAE,GAAG,mBAAmB,GAAG,aAAa,EAAE,CAAC;oBAC1E,MAAM,CAAC,OAAO,EAAE,CAAC;oBACjB,OAAO;gBACT,CAAC;gBACD,SAAS,CAAC,QAAQ,CAAC,cAAc,EAAE,mBAAmB,EAAE,SAAS,CAAC,CAAC;YACrE,CAAC,CAAC;YACF,SAAS,CAAC,QAAQ,CAAC,cAAc,EAAE,mBAAmB,EAAE,SAAS,CAAC,CAAC;QACrE,CAAC;QAED,MAAM,aAAa,GAAG,CAAC,IAAgB,EAAE,QAAiB,EAAQ,EAAE;YAClE,IAAI,QAAQ,IAAI,MAAM,IAAI,CAAC,OAAO;gBAAE,OAAO;YAC3C,IAAI,OAAgC,CAAC;YACrC,IAAI,CAAC;gBACH,OAAO,GAAG,IAAI,CAAC,KAAK,CAAC,gBAAgB,CAAC,IAAI,CAAC,CAA4B,CAAC;YAC1E,CAAC;YAAC,MAAM,CAAC;gBACP,OAAO;YACT,CAAC;YACD,MAAM,KAAK,GAAG,OAAO,CAAC,OAAO,CAAC,CAAC;YAC/B,IAAI,KAAK,KAAK,WAAW,IAAI,KAAK,KAAK,MAAM,IAAI,KAAK,KAAK,MAAM;gBAAE,OAAO;YAC1E,IAAI,KAAK,KAAK,OAAO,EAAE,CAAC;gBACtB,MAAM,KAAK,GAAG,CAAC,OAAO,CAAC,OAAO,CAAC,IAAI,EAAE,CAAuB,CAAC;gBAC7D,QAAQ;oBACN,CAAC,OAAO,OAAO,CAAC,WAAW,CAAC,KAAK,QAAQ,IAAI,OAAO,CAAC,WAAW,CAAC,CAAC;wBAClE,CAAC,OAAO,KAAK,CAAC,SAAS,KAAK,QAAQ,IAAI,KAAK,CAAC,SAAS,CAAC;wBACxD,EAAE,CAAC;gBACL,IAAI,CAAC;oBACH,IAAI,KAAK,CAAC,YAAY,EAAE,CAAC;wBACvB,MAAM,MAAM,GAAG,mBAAmB,CAAC,KAAK,CAAC,CAAC;wBAC1C,SAAS,GAAG,MAAM,CAAC,KAAK,CAAC;wBACzB,gBAAgB,GAAG,MAAM,CAAC,YAAY,CAAC;wBACvC,IAAI,GAAG,qBAAqB,CAAC,SAAS,CAAC,CAAC;oBAC1C,CAAC;yBAAM,CAAC;wBACN,SAAS,GAAG,YAAY,CAAC;wBACzB,gBAAgB,GAAG,sBAAsB,CAAC,YAAY,CAAC,CAAC;wBACxD,IAAI,GAAG,qBAAqB,CAAC,YAAY,CAAC,CAAC;oBAC7C,CAAC;gBACH,CAAC;gBAAC,MAAM,CAAC;oBACP,oEAAoE;oBACpE,qEAAqE;oBACrE,qBAAqB;gBACvB,CAAC;gBACD,WAAW,GAAG,sBAAsB,CAAC;oBACnC,GAAG,KAAK;oBACR,SAAS,EAAE,QAAQ,IAAI,KAAK,CAAC,SAAS;iBACvC,CAAC,CAAC;gBACH,SAAS,GAAG,WAAW,CAAC;gBACxB,OAAO,GAAG,IAAI,CAAC;gBACf,OAAO;YACT,CAAC;YACD,IAAI,KAAK,KAAK,OAAO,EAAE,CAAC;gBACtB,MAAM,KAAK,GAAG,CAAC,OAAO,CAAC,OAAO,CAAC,IAAI,EAAE,CAA4B,CAAC;gBAClE,MAAM,OAAO,GAAG,OAAO,KAAK,CAAC,SAAS,CAAC,KAAK,QAAQ,CAAC,CAAC,CAAC,KAAK,CAAC,SAAS,CAAC,CAAC,CAAC,CAAC,EAAE,CAAC;gBAC7E,IAAI,CAAC,OAAO,IAAI,CAAC,SAAS,IAAI,CAAC,OAAO;oBAAE,OAAO;gBAC/C,MAAM,OAAO,GAAG,aAAa,CAAC,OAAO,CAAC,CAAC;gBACvC,MAAM,OAAO,GAAG,0BAA0B,CAAC,OAAO,EAAE,SAAS,EAAE,IAAI,CAAC,CAAC;gBACrE,MAAM,SAAS,GAAG,sBAAsB,CAAC,gBAAgB,EAAE,OAAO,EAAE,gBAAgB,EAAE,UAAU,CAAC,CAAC;gBAClG,OAAO,CAAC,GAAG,CAAC,IAAI,CAAC,KAAK,CAAC,IAAI,EAAE;oBAC3B,IAAI,EAAE,qBAAqB;oBAC3B,SAAS;oBACT,WAAW,EAAE,IAAI,CAAC,GAAG,EAAE;oBACvB,KAAK,EAAE,mBAAmB,CAAC,SAAS,CAAC;oBACrC,YAAY,EAAE,UAAU;iBACzB,CAAC,CAAC;gBACH,OAAO;YACT,CAAC;YACD,IAAI,KAAK,KAAK,MAAM,EAAE,CAAC;gBACrB,OAAO,GAAG,IAAI,CAAC;gBACf,OAAO,GAAG,KAAK,CAAC;gBAChB,MAAM,CAAC,OAAO,EAAE,CAAC;YACnB,CAAC;QACH,CAAC,CAAC;QACF,WAAW,GAAG,aAAa,CAAC;QAC5B,KAAK,MAAM,MAAM,IAAI,eAAe,CAAC,MAAM,CAAC,CAAC,CAAC,EAAE,CAAC;YAC/C,aAAa,CAAC,MAAM,CAAC,IAAI,EAAE,MAAM,CAAC,QAAQ,CAAC,CAAC;QAC9C,CAAC;IACH,CAAC;IAAC,OAAO,GAAG,EAAE,CAAC;QACb,cAAc,CAAC;YACb,KAAK,EAAE,OAAO;YACd,OAAO,EAAE;gBACP,IAAI,EAAE,MAAM;gBACZ,KAAK,EAAE,wBAAwB;gBAC/B,MAAM,EAAE,GAAG,YAAY,KAAK,CAAC,CAAC,CAAC,GAAG,CAAC,OAAO,CAAC,CAAC,CAAC,MAAM,CAAC,GAAG,CAAC;aACzD;SACF,CAAC,CAAC;QACH,MAAM,CAAC,OAAO,EAAE,CAAC;QACjB,OAAO,EAAE,CAAC;IACZ,CAAC;AACH,CAAC;AAED,SAAS,gBAAgB,CAAC,IAAgB;IACxC,IAAI,OAAO,IAAI,KAAK,QAAQ;QAAE,OAAO,IAAI,CAAC;IAC1C,IAAI,IAAI,YAAY,WAAW;QAAE,OAAO,IAAI,WAAW,EAAE,CAAC,MAAM,CAAC,IAAI,UAAU,CAAC,IAAI,CAAC,CAAC,CAAC;IACvF,OAAO,IAAI,WAAW,EAAE,CAAC,MAAM,CAAC,IAAkB,CAAC,CAAC;AACtD,CAAC;AAED,SAAS,aAAa,CAAC,KAAa;IAClC,MAAM,MAAM,GAAG,IAAI,CAAC,KAAK,CAAC,CAAC;IAC3B,MAAM,KAAK,GAAG,IAAI,UAAU,CAAC,MAAM,CAAC,MAAM,CAAC,CAAC;IAC5C,KAAK,IAAI,CAAC,GAAG,CAAC,EAAE,CAAC,GAAG,MAAM,CAAC,MAAM,EAAE,CAAC,IAAI,CAAC;QAAE,KAAK,CAAC,CAAC,CAAC,GAAG,MAAM,CAAC,UAAU,CAAC,CAAC,CAAC,CAAC;IAC3E,OAAO,KAAK,CAAC;AACf,CAAC;AAED,SAAS,aAAa,CAAC,KAAiB;IACtC,IAAI,MAAM,GAAG,EAAE,CAAC;IAChB,MAAM,SAAS,GAAG,MAAM,CAAC;IACzB,KAAK,IAAI,MAAM,GAAG,CAAC,EAAE,MAAM,GAAG,KAAK,CAAC,MAAM,EAAE,MAAM,IAAI,SAAS,EAAE,CAAC;QAChE,MAAM,IAAI,MAAM,CAAC,YAAY,CAAC,GAAG,KAAK,CAAC,QAAQ,CAAC,MAAM,EAAE,MAAM,GAAG,SAAS,CAAC,CAAC,CAAC;IAC/E,CAAC;IACD,OAAO,IAAI,CAAC,MAAM,CAAC,CAAC;AACtB,CAAC"}
|
|
@@ -0,0 +1,31 @@
|
|
|
1
|
+
import { type Scheduler, type VoiceAgentSession } from "@kuralle-syrinx/core";
|
|
2
|
+
import type { ManagedSocket } from "@kuralle-syrinx/ws";
|
|
3
|
+
import { type BackgroundAudioConfig } from "./background-audio.js";
|
|
4
|
+
import { createWorkersInboundSocket, type WorkersDurableObjectWebSocketContext } from "@kuralle-syrinx/ws/workers";
|
|
5
|
+
import type { SessionStore } from "./session-store.js";
|
|
6
|
+
export interface TwilioEdgeWebSocketOptions {
|
|
7
|
+
readonly sessionStore: SessionStore;
|
|
8
|
+
readonly createSession: (request: Request) => VoiceAgentSession | Promise<VoiceAgentSession>;
|
|
9
|
+
readonly scheduler?: Scheduler;
|
|
10
|
+
/** Engine-side PCM rate (default 16000). Twilio's leg is always 8 kHz μ-law. */
|
|
11
|
+
readonly engineSampleRateHz?: number;
|
|
12
|
+
/**
|
|
13
|
+
* Ambient/thinking bed: mixed (ducked) under assistant speech, and sent as
|
|
14
|
+
* comfort-noise frames between turns — pure digital silence on a phone line
|
|
15
|
+
* reads as "the call died". Thinking loop follows the G3 tool-call cues.
|
|
16
|
+
*/
|
|
17
|
+
readonly backgroundAudio?: BackgroundAudioConfig;
|
|
18
|
+
/** Cadence of idle comfort-noise frames (ms). @default 200 */
|
|
19
|
+
readonly backgroundIdleFrameMs?: number;
|
|
20
|
+
readonly resumeWindowMs?: number;
|
|
21
|
+
readonly keepAliveIntervalMs?: number;
|
|
22
|
+
readonly idleTimeoutMs?: number;
|
|
23
|
+
readonly startupTimeoutMs?: number;
|
|
24
|
+
}
|
|
25
|
+
export interface TwilioEdgeWebSocketUpgrade {
|
|
26
|
+
readonly response: Response;
|
|
27
|
+
readonly controller: ReturnType<typeof createWorkersInboundSocket>["controller"];
|
|
28
|
+
}
|
|
29
|
+
export declare function createTwilioEdgeWebSocketUpgrade(request: Request, options: TwilioEdgeWebSocketOptions, ctx?: WorkersDurableObjectWebSocketContext): TwilioEdgeWebSocketUpgrade;
|
|
30
|
+
export declare function runTwilioEdgeWebSocketConnection(socket: ManagedSocket, request: Request, options: TwilioEdgeWebSocketOptions): Promise<void>;
|
|
31
|
+
//# sourceMappingURL=edge-twilio.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"edge-twilio.d.ts","sourceRoot":"","sources":["../src/edge-twilio.ts"],"names":[],"mappings":"AAYA,OAAO,EAGL,KAAK,SAAS,EACd,KAAK,iBAAiB,EAEvB,MAAM,sBAAsB,CAAC;AAS9B,OAAO,KAAK,EAAE,aAAa,EAAc,MAAM,oBAAoB,CAAC;AACpE,OAAO,EAGL,KAAK,qBAAqB,EAC3B,MAAM,uBAAuB,CAAC;AAC/B,OAAO,EACL,0BAA0B,EAC1B,KAAK,oCAAoC,EAC1C,MAAM,4BAA4B,CAAC;AACpC,OAAO,KAAK,EAAE,YAAY,EAAkB,MAAM,oBAAoB,CAAC;AASvE,MAAM,WAAW,0BAA0B;IACzC,QAAQ,CAAC,YAAY,EAAE,YAAY,CAAC;IACpC,QAAQ,CAAC,aAAa,EAAE,CAAC,OAAO,EAAE,OAAO,KAAK,iBAAiB,GAAG,OAAO,CAAC,iBAAiB,CAAC,CAAC;IAC7F,QAAQ,CAAC,SAAS,CAAC,EAAE,SAAS,CAAC;IAC/B,gFAAgF;IAChF,QAAQ,CAAC,kBAAkB,CAAC,EAAE,MAAM,CAAC;IACrC;;;;OAIG;IACH,QAAQ,CAAC,eAAe,CAAC,EAAE,qBAAqB,CAAC;IACjD,8DAA8D;IAC9D,QAAQ,CAAC,qBAAqB,CAAC,EAAE,MAAM,CAAC;IACxC,QAAQ,CAAC,cAAc,CAAC,EAAE,MAAM,CAAC;IACjC,QAAQ,CAAC,mBAAmB,CAAC,EAAE,MAAM,CAAC;IACtC,QAAQ,CAAC,aAAa,CAAC,EAAE,MAAM,CAAC;IAChC,QAAQ,CAAC,gBAAgB,CAAC,EAAE,MAAM,CAAC;CACpC;AAED,MAAM,WAAW,0BAA0B;IACzC,QAAQ,CAAC,QAAQ,EAAE,QAAQ,CAAC;IAC5B,QAAQ,CAAC,UAAU,EAAE,UAAU,CAAC,OAAO,0BAA0B,CAAC,CAAC,YAAY,CAAC,CAAC;CAClF;AAED,wBAAgB,gCAAgC,CAC9C,OAAO,EAAE,OAAO,EAChB,OAAO,EAAE,0BAA0B,EACnC,GAAG,CAAC,EAAE,oCAAoC,GACzC,0BAA0B,CAI5B;AAED,wBAAsB,gCAAgC,CACpD,MAAM,EAAE,aAAa,EACrB,OAAO,EAAE,OAAO,EAChB,OAAO,EAAE,0BAA0B,GAClC,OAAO,CAAC,IAAI,CAAC,CA0Of"}
|