@ariaflowagents/livekit-plugin-transport-twilio 0.9.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/README.md +221 -0
- package/dist/audio_input.d.ts +42 -0
- package/dist/audio_input.d.ts.map +1 -0
- package/dist/audio_input.js +117 -0
- package/dist/audio_input.js.map +1 -0
- package/dist/audio_output.d.ts +47 -0
- package/dist/audio_output.d.ts.map +1 -0
- package/dist/audio_output.js +162 -0
- package/dist/audio_output.js.map +1 -0
- package/dist/codec/g711.d.ts +36 -0
- package/dist/codec/g711.d.ts.map +1 -0
- package/dist/codec/g711.js +137 -0
- package/dist/codec/g711.js.map +1 -0
- package/dist/index.d.ts +74 -0
- package/dist/index.d.ts.map +1 -0
- package/dist/index.js +76 -0
- package/dist/index.js.map +1 -0
- package/dist/server.d.ts +42 -0
- package/dist/server.d.ts.map +1 -0
- package/dist/server.js +125 -0
- package/dist/server.js.map +1 -0
- package/dist/text_output.d.ts +32 -0
- package/dist/text_output.d.ts.map +1 -0
- package/dist/text_output.js +72 -0
- package/dist/text_output.js.map +1 -0
- package/dist/transport_adapter.d.ts +95 -0
- package/dist/transport_adapter.d.ts.map +1 -0
- package/dist/transport_adapter.js +174 -0
- package/dist/transport_adapter.js.map +1 -0
- package/dist/twilio_protocol.d.ts +130 -0
- package/dist/twilio_protocol.d.ts.map +1 -0
- package/dist/twilio_protocol.js +74 -0
- package/dist/twilio_protocol.js.map +1 -0
- package/package.json +43 -0
|
@@ -0,0 +1,137 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* G.711 μ-law codec implementation for Twilio Media Streams.
|
|
3
|
+
*
|
|
4
|
+
* Twilio uses G.711 μ-law (mu-law) encoding at 8kHz.
|
|
5
|
+
* This codec provides bidirectional conversion between PCM Int16
|
|
6
|
+
* and μ-law encoded Uint8.
|
|
7
|
+
*/
|
|
8
|
+
const MULAW_BIAS = 0x84;
|
|
9
|
+
const MULAW_CLIP = 32635;
|
|
10
|
+
/**
|
|
11
|
+
* μ-law compression table (precomputed for performance).
|
|
12
|
+
* Maps linear 14-bit samples to 8-bit μ-law values.
|
|
13
|
+
*/
|
|
14
|
+
const MULAW_COMPRESS_TABLE = new Int8Array([
|
|
15
|
+
-3, -3, -3, -3, -3, -3, -3, -3, -3, -3, -3, -3, -3, -3, -3, -3,
|
|
16
|
+
-2, -2, -2, -2, -2, -2, -2, -2, -1, -1, -1, -1, -1, -1, -1, -1,
|
|
17
|
+
-1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1,
|
|
18
|
+
-1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1,
|
|
19
|
+
-1, -1, -1, -1, -0, -0, -0, -0, -0, -0, -0, -0, -0, -0, -0, -0,
|
|
20
|
+
-0, -0, -0, -0, -0, -0, -0, -0, -0, -0, -0, -0, -0, -0, -0, -0,
|
|
21
|
+
-0, -0, -0, -0, -0, -0, -0, -0, -0, -0, -0, -0, -0, -0, -0, -0,
|
|
22
|
+
-0, -0, -0, -0, -0, -0, -0, -0, -0, -0, -0, -0, -0, -0, -0, -0,
|
|
23
|
+
0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
|
|
24
|
+
0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
|
|
25
|
+
0, 0, 0, 0, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1,
|
|
26
|
+
1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1,
|
|
27
|
+
1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 2, 2, 2, 2, 2, 2,
|
|
28
|
+
2, 2, 2, 2, 2, 2, 2, 2, 3, 3, 3, 3, 3, 3, 3, 3,
|
|
29
|
+
3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3,
|
|
30
|
+
3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3,
|
|
31
|
+
]);
|
|
32
|
+
/**
|
|
33
|
+
* μ-law expansion table (precomputed for performance).
|
|
34
|
+
* Maps 8-bit μ-law values to linear 16-bit PCM samples.
|
|
35
|
+
*/
|
|
36
|
+
const MULAW_EXPAND_TABLE = new Int16Array([
|
|
37
|
+
-32124, -31100, -30076, -29052, -28028, -27004, -25980, -24956,
|
|
38
|
+
-23932, -22908, -21884, -20860, -19836, -18812, -17788, -16764,
|
|
39
|
+
-15996, -15484, -14972, -14460, -13948, -13436, -12924, -12412,
|
|
40
|
+
-11900, -11388, -10876, -10364, -9852, -9340, -8828, -8316,
|
|
41
|
+
-7932, -7676, -7420, -7164, -6908, -6652, -6396, -6140,
|
|
42
|
+
-5884, -5628, -5372, -5116, -4860, -4604, -4348, -4092,
|
|
43
|
+
-3900, -3772, -3644, -3516, -3388, -3260, -3132, -3004,
|
|
44
|
+
-2876, -2748, -2620, -2492, -2364, -2236, -2108, -1980,
|
|
45
|
+
-1884, -1820, -1756, -1692, -1628, -1564, -1500, -1436,
|
|
46
|
+
-1372, -1308, -1244, -1180, -1116, -1052, -988, -924,
|
|
47
|
+
-876, -844, -812, -780, -748, -716, -684, -652,
|
|
48
|
+
-620, -588, -556, -524, -492, -460, -428, -396,
|
|
49
|
+
-372, -356, -340, -324, -308, -292, -276, -260,
|
|
50
|
+
-244, -228, -212, -196, -180, -164, -148, -132,
|
|
51
|
+
-120, -112, -104, -96, -88, -80, -72, -64,
|
|
52
|
+
-56, -48, -40, -32, -24, -16, -8, 0,
|
|
53
|
+
0, 8, 16, 24, 32, 40, 48, 56,
|
|
54
|
+
64, 72, 80, 88, 96, 104, 112, 120,
|
|
55
|
+
132, 148, 164, 180, 196, 212, 228, 244,
|
|
56
|
+
260, 276, 292, 308, 324, 340, 356, 372,
|
|
57
|
+
396, 428, 460, 492, 524, 556, 588, 620,
|
|
58
|
+
652, 684, 716, 748, 780, 812, 844, 876,
|
|
59
|
+
924, 988, 1052, 1116, 1180, 1244, 1308, 1372,
|
|
60
|
+
1436, 1500, 1564, 1628, 1692, 1756, 1820, 1884,
|
|
61
|
+
1980, 2108, 2236, 2364, 2492, 2620, 2748, 2876,
|
|
62
|
+
3004, 3132, 3260, 3388, 3516, 3644, 3772, 3900,
|
|
63
|
+
4092, 4348, 4604, 4860, 5116, 5372, 5628, 5884,
|
|
64
|
+
6140, 6396, 6652, 6908, 7164, 7420, 7676, 7932,
|
|
65
|
+
8316, 8828, 9340, 9852, 10364, 10876, 11388, 11900,
|
|
66
|
+
12412, 12924, 13436, 13948, 14460, 14972, 15484, 15996,
|
|
67
|
+
16764, 17788, 18812, 19836, 20860, 21884, 22908, 23932,
|
|
68
|
+
24956, 25980, 27004, 28028, 29052, 30076, 31100, 32124,
|
|
69
|
+
]);
|
|
70
|
+
/**
|
|
71
|
+
* Encode a PCM Int16 sample to μ-law Uint8.
|
|
72
|
+
*
|
|
73
|
+
* @param sample - 16-bit PCM sample (-32768 to 32767)
|
|
74
|
+
* @returns μ-law encoded byte (0-255)
|
|
75
|
+
*/
|
|
76
|
+
export function mulawEncode(sample) {
|
|
77
|
+
// Add bias to center around zero
|
|
78
|
+
sample += MULAW_BIAS;
|
|
79
|
+
// Clip to valid range
|
|
80
|
+
if (sample > MULAW_CLIP)
|
|
81
|
+
sample = MULAW_CLIP;
|
|
82
|
+
if (sample < -MULAW_CLIP)
|
|
83
|
+
sample = -MULAW_CLIP;
|
|
84
|
+
// Convert to positive value
|
|
85
|
+
const sign = sample < 0 ? 0x80 : 0x00;
|
|
86
|
+
if (sign)
|
|
87
|
+
sample = -sample;
|
|
88
|
+
// Find exponent and mantissa
|
|
89
|
+
let exponent = 7;
|
|
90
|
+
for (let i = 0; i < 7; i++) {
|
|
91
|
+
if (sample <= (0x01 << (i + 1))) {
|
|
92
|
+
exponent = i;
|
|
93
|
+
break;
|
|
94
|
+
}
|
|
95
|
+
}
|
|
96
|
+
const mantissa = (sample >> (exponent + 1)) & 0x0F;
|
|
97
|
+
// Compress to 8-bit μ-law
|
|
98
|
+
const mulaw = ~(sign | (exponent << 4) | mantissa);
|
|
99
|
+
return mulaw & 0xFF;
|
|
100
|
+
}
|
|
101
|
+
/**
|
|
102
|
+
* Decode a μ-law Uint8 to PCM Int16.
|
|
103
|
+
*
|
|
104
|
+
* @param mulaw - μ-law encoded byte (0-255)
|
|
105
|
+
* @returns 16-bit PCM sample
|
|
106
|
+
*/
|
|
107
|
+
export function mulawDecode(mulaw) {
|
|
108
|
+
// Expand using lookup table
|
|
109
|
+
return MULAW_EXPAND_TABLE[mulaw & 0xFF];
|
|
110
|
+
}
|
|
111
|
+
/**
|
|
112
|
+
* Encode an array of PCM samples to μ-law.
|
|
113
|
+
*
|
|
114
|
+
* @param pcm - Int16Array of PCM samples
|
|
115
|
+
* @returns Uint8Array of μ-law encoded bytes
|
|
116
|
+
*/
|
|
117
|
+
export function mulawEncodeArray(pcm) {
|
|
118
|
+
const mulaw = new Uint8Array(pcm.length);
|
|
119
|
+
for (let i = 0; i < pcm.length; i++) {
|
|
120
|
+
mulaw[i] = mulawEncode(pcm[i]);
|
|
121
|
+
}
|
|
122
|
+
return mulaw;
|
|
123
|
+
}
|
|
124
|
+
/**
|
|
125
|
+
* Decode an array of μ-law bytes to PCM.
|
|
126
|
+
*
|
|
127
|
+
* @param mulaw - Uint8Array of μ-law encoded bytes
|
|
128
|
+
* @returns Int16Array of PCM samples
|
|
129
|
+
*/
|
|
130
|
+
export function mulawDecodeArray(mulaw) {
|
|
131
|
+
const pcm = new Int16Array(mulaw.length);
|
|
132
|
+
for (let i = 0; i < mulaw.length; i++) {
|
|
133
|
+
pcm[i] = mulawDecode(mulaw[i]);
|
|
134
|
+
}
|
|
135
|
+
return pcm;
|
|
136
|
+
}
|
|
137
|
+
//# sourceMappingURL=g711.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"g711.js","sourceRoot":"","sources":["../../src/codec/g711.ts"],"names":[],"mappings":"AAAA;;;;;;GAMG;AAEH,MAAM,UAAU,GAAG,IAAI,CAAC;AACxB,MAAM,UAAU,GAAG,KAAK,CAAC;AAEzB;;;GAGG;AACH,MAAM,oBAAoB,GAAG,IAAI,SAAS,CAAC;IACzC,CAAC,CAAC,EAAE,CAAC,CAAC,EAAE,CAAC,CAAC,EAAE,CAAC,CAAC,EAAE,CAAC,CAAC,EAAE,CAAC,CAAC,EAAE,CAAC,CAAC,EAAE,CAAC,CAAC,EAAE,CAAC,CAAC,EAAE,CAAC,CAAC,EAAE,CAAC,CAAC,EAAE,CAAC,CAAC,EAAE,CAAC,CAAC,EAAE,CAAC,CAAC,EAAE,CAAC,CAAC,EAAE,CAAC,CAAC;IAC9D,CAAC,CAAC,EAAE,CAAC,CAAC,EAAE,CAAC,CAAC,EAAE,CAAC,CAAC,EAAE,CAAC,CAAC,EAAE,CAAC,CAAC,EAAE,CAAC,CAAC,EAAE,CAAC,CAAC,EAAE,CAAC,CAAC,EAAE,CAAC,CAAC,EAAE,CAAC,CAAC,EAAE,CAAC,CAAC,EAAE,CAAC,CAAC,EAAE,CAAC,CAAC,EAAE,CAAC,CAAC,EAAE,CAAC,CAAC;IAC9D,CAAC,CAAC,EAAE,CAAC,CAAC,EAAE,CAAC,CAAC,EAAE,CAAC,CAAC,EAAE,CAAC,CAAC,EAAE,CAAC,CAAC,EAAE,CAAC,CAAC,EAAE,CAAC,CAAC,EAAE,CAAC,CAAC,EAAE,CAAC,CAAC,EAAE,CAAC,CAAC,EAAE,CAAC,CAAC,EAAE,CAAC,CAAC,EAAE,CAAC,CAAC,EAAE,CAAC,CAAC,EAAE,CAAC,CAAC;IAC9D,CAAC,CAAC,EAAE,CAAC,CAAC,EAAE,CAAC,CAAC,EAAE,CAAC,CAAC,EAAE,CAAC,CAAC,EAAE,CAAC,CAAC,EAAE,CAAC,CAAC,EAAE,CAAC,CAAC,EAAE,CAAC,CAAC,EAAE,CAAC,CAAC,EAAE,CAAC,CAAC,EAAE,CAAC,CAAC,EAAE,CAAC,CAAC,EAAE,CAAC,CAAC,EAAE,CAAC,CAAC,EAAE,CAAC,CAAC;IAC9D,CAAC,CAAC,EAAE,CAAC,CAAC,EAAE,CAAC,CAAC,EAAE,CAAC,CAAC,EAAE,CAAC,CAAC,EAAE,CAAC,CAAC,EAAE,CAAC,CAAC,EAAE,CAAC,CAAC,EAAE,CAAC,CAAC,EAAE,CAAC,CAAC,EAAE,CAAC,CAAC,EAAE,CAAC,CAAC,EAAE,CAAC,CAAC,EAAE,CAAC,CAAC,EAAE,CAAC,CAAC,EAAE,CAAC,CAAC;IAC9D,CAAC,CAAC,EAAE,CAAC,CAAC,EAAE,CAAC,CAAC,EAAE,CAAC,CAAC,EAAE,CAAC,CAAC,EAAE,CAAC,CAAC,EAAE,CAAC,CAAC,EAAE,CAAC,CAAC,EAAE,CAAC,CAAC,EAAE,CAAC,CAAC,EAAE,CAAC,CAAC,EAAE,CAAC,CAAC,EAAE,CAAC,CAAC,EAAE,CAAC,CAAC,EAAE,CAAC,CAAC,EAAE,CAAC,CAAC;IAC9D,CAAC,CAAC,EAAE,CAAC,CAAC,EAAE,CAAC,CAAC,EAAE,CAAC,CAAC,EAAE,CAAC,CAAC,EAAE,CAAC,CAAC,EAAE,CAAC,CAAC,EAAE,CAAC,CAAC,EAAE,CAAC,CAAC,EAAE,CAAC,CAAC,EAAE,CAAC,CAAC,EAAE,CAAC,CAAC,EAAE,CAAC,CAAC,EAAE,CAAC,CAAC,EAAE,CAAC,CAAC,EAAE,CAAC,CAAC;IAC9D,CAAC,CAAC,EAAE,CAAC,CAAC,EAAE,CAAC,CAAC,EAAE,CAAC,CAAC,EAAE,CAAC,CAAC,EAAE,CAAC,CAAC,EAAE,CAAC,CAAC,EAAE,CAAC,CAAC,EAAE,CAAC,CAAC,EAAE,CAAC,CAAC,EAAE,CAAC,CAAC,EAAE,CAAC,CAAC,EAAE,CAAC,CAAC,EAAE,CAAC,CAAC,EAAE,CAAC,CAAC,EAAE,CAAC,CAAC;IAC9D,CAAC,EAAE,CAAC,EAAE,CAAC,EAAE,CAAC,EAAE,CAAC,EAAE,CAAC,EAAE,CAAC,EAAE,CAAC,EAAE,CAAC,EAAE,CAAC,EAAE,CAAC,EAAE,CAAC,EAAE,CAAC,EAAE,CAAC,EAAE,CAAC,EAAE,CAAC;IAC9C,CAAC,EAAE,CAAC,EAAE,CAAC,EAAE,CAAC,EAAE,CAAC,EAAE,CAAC,EAAE,CAAC,EAAE,CAAC,EAAE,CAAC,EAAE,CAAC,EAAE,CAAC,EAAE,CAAC,EAAE,CAAC,EAAE,CAAC,EAAE,CAAC,EAAE,CAAC;IAC9C,CAAC,EAAE,CAAC,EAA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"}
|
package/dist/index.d.ts
ADDED
|
@@ -0,0 +1,74 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Twilio Media Streams Transport for AriaFlow Agents
|
|
3
|
+
*
|
|
4
|
+
* Enables AriaFlow voice agents to work with Twilio Media Streams API.
|
|
5
|
+
* Compatible with Cloudflare Workers, Node.js, and any WebSocket-enabled runtime.
|
|
6
|
+
*
|
|
7
|
+
* Features:
|
|
8
|
+
* - G.711 μ-law codec support (Twilio standard)
|
|
9
|
+
* - Automatic resampling (8kHz ↔ 24kHz)
|
|
10
|
+
* - Cloudflare Workers compatible
|
|
11
|
+
* - Session management integration
|
|
12
|
+
*
|
|
13
|
+
* Basic usage (Cloudflare Workers):
|
|
14
|
+
* ```typescript
|
|
15
|
+
* import { createTwilioWorker } from '@ariaflow/livekit-plugin-transport-twilio/cloudflare';
|
|
16
|
+
*
|
|
17
|
+
* export default createTwilioWorker({
|
|
18
|
+
* agent: () => createAriaFlowSession({...}),
|
|
19
|
+
* });
|
|
20
|
+
* ```
|
|
21
|
+
*
|
|
22
|
+
* Basic usage (Node.js/any WebSocket):
|
|
23
|
+
* ```typescript
|
|
24
|
+
* import { WebSocketServer } from 'ws';
|
|
25
|
+
* import { TwilioTransportAdapter } from '@ariaflow/livekit-plugin-transport-twilio';
|
|
26
|
+
* import { createAriaFlowSession } from '@ariaflow/livekit-plugin';
|
|
27
|
+
*
|
|
28
|
+
* const wss = new WebSocketServer({ port: 8080 });
|
|
29
|
+
*
|
|
30
|
+
* wss.on('connection', (ws, req) => {
|
|
31
|
+
* const transport = new TwilioTransportAdapter({
|
|
32
|
+
* send: (msg) => ws.send(msg),
|
|
33
|
+
* });
|
|
34
|
+
*
|
|
35
|
+
* ws.on('message', (data) => {
|
|
36
|
+
* transport.handleMessage(data.toString());
|
|
37
|
+
* });
|
|
38
|
+
*
|
|
39
|
+
* const { agent, sessionOptions } = createAriaFlowSession({
|
|
40
|
+
* runtime: agentConfig,
|
|
41
|
+
* stt: new GeminiLiveSTT(),
|
|
42
|
+
* tts: new GeminiLiveTTS(),
|
|
43
|
+
* });
|
|
44
|
+
*
|
|
45
|
+
* await sessionManager.startSession(transport, agent, sessionOptions);
|
|
46
|
+
* });
|
|
47
|
+
* ```
|
|
48
|
+
*
|
|
49
|
+
* Basic usage (TwilioAgentServer):
|
|
50
|
+
* ```typescript
|
|
51
|
+
* import { TwilioAgentServer } from '@ariaflow/livekit-plugin-transport-twilio';
|
|
52
|
+
* import { createAriaFlowSession } from '@ariaflow/livekit-plugin';
|
|
53
|
+
*
|
|
54
|
+
* const server = new TwilioAgentServer({ port: 8080 });
|
|
55
|
+
*
|
|
56
|
+
* server.onCall(async (callId, transport) => {
|
|
57
|
+
* const { agent, sessionOptions } = createAriaFlowSession({...});
|
|
58
|
+
* await server.startSession(callId, agent, sessionOptions);
|
|
59
|
+
* });
|
|
60
|
+
*
|
|
61
|
+
* await server.listen();
|
|
62
|
+
* ```
|
|
63
|
+
*/
|
|
64
|
+
export { TwilioAgentServer } from './server.js';
|
|
65
|
+
export type { TwilioServerOptions } from './server.js';
|
|
66
|
+
export { TwilioTransportAdapter } from './transport_adapter.js';
|
|
67
|
+
export type { TwilioTransportOptions } from './transport_adapter.js';
|
|
68
|
+
export { TwilioAudioInput } from './audio_input.js';
|
|
69
|
+
export { TwilioAudioOutput } from './audio_output.js';
|
|
70
|
+
export { TwilioTextOutput } from './text_output.js';
|
|
71
|
+
export { parseTwilioMessage, isMediaEvent, extractMediaPayload, createClearMessage, createMarkMessage, } from './twilio_protocol.js';
|
|
72
|
+
export type { TwilioEvent, TwilioMediaEvent, TwilioConnectedEvent, TwilioStartEvent, TwilioStopEvent, TwilioMarkEvent, TwilioClearEvent, } from './twilio_protocol.js';
|
|
73
|
+
export { mulawEncodeArray, mulawDecodeArray } from '@ariaflow/livekit-plugin/codec/g711';
|
|
74
|
+
//# sourceMappingURL=index.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GA8DG;AAGH,OAAO,EAAE,iBAAiB,EAAE,MAAM,aAAa,CAAC;AAChD,YAAY,EAAE,mBAAmB,EAAE,MAAM,aAAa,CAAC;AAGvD,OAAO,EAAE,sBAAsB,EAAE,MAAM,wBAAwB,CAAC;AAChE,YAAY,EAAE,sBAAsB,EAAE,MAAM,wBAAwB,CAAC;AAGrE,OAAO,EAAE,gBAAgB,EAAE,MAAM,kBAAkB,CAAC;AACpD,OAAO,EAAE,iBAAiB,EAAE,MAAM,mBAAmB,CAAC;AACtD,OAAO,EAAE,gBAAgB,EAAE,MAAM,kBAAkB,CAAC;AAGpD,OAAO,EACL,kBAAkB,EAClB,YAAY,EACZ,mBAAmB,EACnB,kBAAkB,EAClB,iBAAiB,GAClB,MAAM,sBAAsB,CAAC;AAC9B,YAAY,EACV,WAAW,EACX,gBAAgB,EAChB,oBAAoB,EACpB,gBAAgB,EAChB,eAAe,EACf,eAAe,EACf,gBAAgB,GACjB,MAAM,sBAAsB,CAAC;AAG9B,OAAO,EAAE,gBAAgB,EAAE,gBAAgB,EAAE,MAAM,qCAAqC,CAAC"}
|
package/dist/index.js
ADDED
|
@@ -0,0 +1,76 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Twilio Media Streams Transport for AriaFlow Agents
|
|
3
|
+
*
|
|
4
|
+
* Enables AriaFlow voice agents to work with Twilio Media Streams API.
|
|
5
|
+
* Compatible with Cloudflare Workers, Node.js, and any WebSocket-enabled runtime.
|
|
6
|
+
*
|
|
7
|
+
* Features:
|
|
8
|
+
* - G.711 μ-law codec support (Twilio standard)
|
|
9
|
+
* - Automatic resampling (8kHz ↔ 24kHz)
|
|
10
|
+
* - Cloudflare Workers compatible
|
|
11
|
+
* - Session management integration
|
|
12
|
+
*
|
|
13
|
+
* Basic usage (Cloudflare Workers):
|
|
14
|
+
* ```typescript
|
|
15
|
+
* import { createTwilioWorker } from '@ariaflow/livekit-plugin-transport-twilio/cloudflare';
|
|
16
|
+
*
|
|
17
|
+
* export default createTwilioWorker({
|
|
18
|
+
* agent: () => createAriaFlowSession({...}),
|
|
19
|
+
* });
|
|
20
|
+
* ```
|
|
21
|
+
*
|
|
22
|
+
* Basic usage (Node.js/any WebSocket):
|
|
23
|
+
* ```typescript
|
|
24
|
+
* import { WebSocketServer } from 'ws';
|
|
25
|
+
* import { TwilioTransportAdapter } from '@ariaflow/livekit-plugin-transport-twilio';
|
|
26
|
+
* import { createAriaFlowSession } from '@ariaflow/livekit-plugin';
|
|
27
|
+
*
|
|
28
|
+
* const wss = new WebSocketServer({ port: 8080 });
|
|
29
|
+
*
|
|
30
|
+
* wss.on('connection', (ws, req) => {
|
|
31
|
+
* const transport = new TwilioTransportAdapter({
|
|
32
|
+
* send: (msg) => ws.send(msg),
|
|
33
|
+
* });
|
|
34
|
+
*
|
|
35
|
+
* ws.on('message', (data) => {
|
|
36
|
+
* transport.handleMessage(data.toString());
|
|
37
|
+
* });
|
|
38
|
+
*
|
|
39
|
+
* const { agent, sessionOptions } = createAriaFlowSession({
|
|
40
|
+
* runtime: agentConfig,
|
|
41
|
+
* stt: new GeminiLiveSTT(),
|
|
42
|
+
* tts: new GeminiLiveTTS(),
|
|
43
|
+
* });
|
|
44
|
+
*
|
|
45
|
+
* await sessionManager.startSession(transport, agent, sessionOptions);
|
|
46
|
+
* });
|
|
47
|
+
* ```
|
|
48
|
+
*
|
|
49
|
+
* Basic usage (TwilioAgentServer):
|
|
50
|
+
* ```typescript
|
|
51
|
+
* import { TwilioAgentServer } from '@ariaflow/livekit-plugin-transport-twilio';
|
|
52
|
+
* import { createAriaFlowSession } from '@ariaflow/livekit-plugin';
|
|
53
|
+
*
|
|
54
|
+
* const server = new TwilioAgentServer({ port: 8080 });
|
|
55
|
+
*
|
|
56
|
+
* server.onCall(async (callId, transport) => {
|
|
57
|
+
* const { agent, sessionOptions } = createAriaFlowSession({...});
|
|
58
|
+
* await server.startSession(callId, agent, sessionOptions);
|
|
59
|
+
* });
|
|
60
|
+
*
|
|
61
|
+
* await server.listen();
|
|
62
|
+
* ```
|
|
63
|
+
*/
|
|
64
|
+
// Server
|
|
65
|
+
export { TwilioAgentServer } from './server.js';
|
|
66
|
+
// Core transport
|
|
67
|
+
export { TwilioTransportAdapter } from './transport_adapter.js';
|
|
68
|
+
// Audio I/O
|
|
69
|
+
export { TwilioAudioInput } from './audio_input.js';
|
|
70
|
+
export { TwilioAudioOutput } from './audio_output.js';
|
|
71
|
+
export { TwilioTextOutput } from './text_output.js';
|
|
72
|
+
// Protocol utilities
|
|
73
|
+
export { parseTwilioMessage, isMediaEvent, extractMediaPayload, createClearMessage, createMarkMessage, } from './twilio_protocol.js';
|
|
74
|
+
// Codec (re-exported from canonical source)
|
|
75
|
+
export { mulawEncodeArray, mulawDecodeArray } from '@ariaflow/livekit-plugin/codec/g711';
|
|
76
|
+
//# sourceMappingURL=index.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"index.js","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GA8DG;AAEH,SAAS;AACT,OAAO,EAAE,iBAAiB,EAAE,MAAM,aAAa,CAAC;AAGhD,iBAAiB;AACjB,OAAO,EAAE,sBAAsB,EAAE,MAAM,wBAAwB,CAAC;AAGhE,YAAY;AACZ,OAAO,EAAE,gBAAgB,EAAE,MAAM,kBAAkB,CAAC;AACpD,OAAO,EAAE,iBAAiB,EAAE,MAAM,mBAAmB,CAAC;AACtD,OAAO,EAAE,gBAAgB,EAAE,MAAM,kBAAkB,CAAC;AAEpD,qBAAqB;AACrB,OAAO,EACL,kBAAkB,EAClB,YAAY,EACZ,mBAAmB,EACnB,kBAAkB,EAClB,iBAAiB,GAClB,MAAM,sBAAsB,CAAC;AAW9B,4CAA4C;AAC5C,OAAO,EAAE,gBAAgB,EAAE,gBAAgB,EAAE,MAAM,qCAAqC,CAAC"}
|
package/dist/server.d.ts
ADDED
|
@@ -0,0 +1,42 @@
|
|
|
1
|
+
import { SessionManager, type AriaFlowVoiceSession } from '@ariaflow/livekit-plugin';
|
|
2
|
+
import type { voice } from '@livekit/agents';
|
|
3
|
+
import { TwilioTransportAdapter } from './transport_adapter.js';
|
|
4
|
+
/**
|
|
5
|
+
* Options for TwilioAgentServer
|
|
6
|
+
*/
|
|
7
|
+
export interface TwilioServerOptions {
|
|
8
|
+
port?: number;
|
|
9
|
+
host?: string;
|
|
10
|
+
}
|
|
11
|
+
/**
|
|
12
|
+
* A WebSocket server that accepts Twilio Media Streams connections and creates agent sessions.
|
|
13
|
+
*
|
|
14
|
+
* Usage:
|
|
15
|
+
* const server = new TwilioAgentServer({ port: 8080 });
|
|
16
|
+
*
|
|
17
|
+
* server.onCall(async (callId, transport) => {
|
|
18
|
+
* const voiceSession = new AriaFlowVoiceSession({
|
|
19
|
+
* runtime: runtime,
|
|
20
|
+
* stt: new GeminiLiveSTT(),
|
|
21
|
+
* tts: new GeminiLiveTTS(),
|
|
22
|
+
* });
|
|
23
|
+
* await server.startSession(callId, voiceSession);
|
|
24
|
+
* });
|
|
25
|
+
*
|
|
26
|
+
* await server.listen();
|
|
27
|
+
*/
|
|
28
|
+
export declare class TwilioAgentServer {
|
|
29
|
+
private options;
|
|
30
|
+
private wss;
|
|
31
|
+
private sessionManager;
|
|
32
|
+
private callHandler;
|
|
33
|
+
private callIdCounter;
|
|
34
|
+
constructor(options?: TwilioServerOptions);
|
|
35
|
+
onCall(handler: (callId: string, transport: TwilioTransportAdapter) => void | Promise<void>): void;
|
|
36
|
+
startSession(callId: string, voiceSession: AriaFlowVoiceSession): Promise<voice.AgentSession>;
|
|
37
|
+
private transports;
|
|
38
|
+
listen(): Promise<void>;
|
|
39
|
+
get sessions(): SessionManager;
|
|
40
|
+
close(): Promise<void>;
|
|
41
|
+
}
|
|
42
|
+
//# sourceMappingURL=server.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"server.d.ts","sourceRoot":"","sources":["../src/server.ts"],"names":[],"mappings":"AACA,OAAO,EAAE,cAAc,EAAE,KAAK,oBAAoB,EAAE,MAAM,0BAA0B,CAAC;AACrF,OAAO,KAAK,EAAE,KAAK,EAAE,MAAM,iBAAiB,CAAC;AAE7C,OAAO,EAAE,sBAAsB,EAAE,MAAM,wBAAwB,CAAC;AAGhE;;GAEG;AACH,MAAM,WAAW,mBAAmB;IAClC,IAAI,CAAC,EAAE,MAAM,CAAC;IACd,IAAI,CAAC,EAAE,MAAM,CAAC;CACf;AAED;;;;;;;;;;;;;;;;GAgBG;AACH,qBAAa,iBAAiB;IAQhB,OAAO,CAAC,OAAO;IAP3B,OAAO,CAAC,GAAG,CAAyB;IACpC,OAAO,CAAC,cAAc,CAAwC;IAC9D,OAAO,CAAC,WAAW,CAEH;IAChB,OAAO,CAAC,aAAa,CAAK;gBAEN,OAAO,GAAE,mBAAwB;IAErD,MAAM,CACJ,OAAO,EAAE,CAAC,MAAM,EAAE,MAAM,EAAE,SAAS,EAAE,sBAAsB,KAAK,IAAI,GAAG,OAAO,CAAC,IAAI,CAAC,GACnF,IAAI;IAID,YAAY,CAChB,MAAM,EAAE,MAAM,EACd,YAAY,EAAE,oBAAoB,GACjC,OAAO,CAAC,KAAK,CAAC,YAAY,CAAC;IAQ9B,OAAO,CAAC,UAAU,CAA6C;IAEzD,MAAM,IAAI,OAAO,CAAC,IAAI,CAAC;IAoF7B,IAAI,QAAQ,IAAI,cAAc,CAE7B;IAEK,KAAK,IAAI,OAAO,CAAC,IAAI,CAAC;CAM7B"}
|
package/dist/server.js
ADDED
|
@@ -0,0 +1,125 @@
|
|
|
1
|
+
import { WebSocketServer as WSServer } from 'ws';
|
|
2
|
+
import { SessionManager } from '@ariaflow/livekit-plugin';
|
|
3
|
+
import { TwilioTransportAdapter } from './transport_adapter.js';
|
|
4
|
+
/**
|
|
5
|
+
* A WebSocket server that accepts Twilio Media Streams connections and creates agent sessions.
|
|
6
|
+
*
|
|
7
|
+
* Usage:
|
|
8
|
+
* const server = new TwilioAgentServer({ port: 8080 });
|
|
9
|
+
*
|
|
10
|
+
* server.onCall(async (callId, transport) => {
|
|
11
|
+
* const voiceSession = new AriaFlowVoiceSession({
|
|
12
|
+
* runtime: runtime,
|
|
13
|
+
* stt: new GeminiLiveSTT(),
|
|
14
|
+
* tts: new GeminiLiveTTS(),
|
|
15
|
+
* });
|
|
16
|
+
* await server.startSession(callId, voiceSession);
|
|
17
|
+
* });
|
|
18
|
+
*
|
|
19
|
+
* await server.listen();
|
|
20
|
+
*/
|
|
21
|
+
export class TwilioAgentServer {
|
|
22
|
+
options;
|
|
23
|
+
wss = null;
|
|
24
|
+
sessionManager = new SessionManager();
|
|
25
|
+
callHandler = null;
|
|
26
|
+
callIdCounter = 0;
|
|
27
|
+
constructor(options = {}) {
|
|
28
|
+
this.options = options;
|
|
29
|
+
}
|
|
30
|
+
onCall(handler) {
|
|
31
|
+
this.callHandler = handler;
|
|
32
|
+
}
|
|
33
|
+
async startSession(callId, voiceSession) {
|
|
34
|
+
const transport = this.transports.get(callId);
|
|
35
|
+
if (!transport) {
|
|
36
|
+
throw new Error(`Transport not found for call: ${callId}`);
|
|
37
|
+
}
|
|
38
|
+
return this.sessionManager.startSession(transport, voiceSession);
|
|
39
|
+
}
|
|
40
|
+
transports = new Map();
|
|
41
|
+
async listen() {
|
|
42
|
+
const port = this.options.port ?? 8080;
|
|
43
|
+
const host = this.options.host ?? '0.0.0.0';
|
|
44
|
+
this.wss = new WSServer({ port, host });
|
|
45
|
+
this.wss.on('connection', async (ws, req) => {
|
|
46
|
+
const callId = `call-${++this.callIdCounter}`;
|
|
47
|
+
console.log(`[TwilioServer] New connection: ${callId}`);
|
|
48
|
+
const transport = new TwilioTransportAdapter({
|
|
49
|
+
id: callId,
|
|
50
|
+
send: (message) => {
|
|
51
|
+
if (ws.readyState === ws.OPEN) {
|
|
52
|
+
ws.send(message);
|
|
53
|
+
}
|
|
54
|
+
},
|
|
55
|
+
});
|
|
56
|
+
// Store transport
|
|
57
|
+
this.transports.set(callId, transport);
|
|
58
|
+
// Handle incoming messages from Twilio
|
|
59
|
+
ws.on('message', (data) => {
|
|
60
|
+
try {
|
|
61
|
+
const message = data.toString();
|
|
62
|
+
// Parse to detect start event
|
|
63
|
+
const event = JSON.parse(message);
|
|
64
|
+
// Log events
|
|
65
|
+
if (event.event === 'connected') {
|
|
66
|
+
console.log(`[TwilioServer] [${callId}] Connected to Twilio`);
|
|
67
|
+
}
|
|
68
|
+
else if (event.event === 'start') {
|
|
69
|
+
const streamSid = event.start?.streamSid ?? event.streamSid ?? '(missing)';
|
|
70
|
+
console.log(`[TwilioServer] [${callId}] Stream started: ${streamSid}`);
|
|
71
|
+
}
|
|
72
|
+
else if (event.event === 'stop') {
|
|
73
|
+
console.log(`[TwilioServer] [${callId}] Stream stopped`);
|
|
74
|
+
}
|
|
75
|
+
// Route to transport
|
|
76
|
+
transport.handleMessage(message);
|
|
77
|
+
// Trigger call handler on start event (when streamSid is available)
|
|
78
|
+
if (event.event === 'start' && this.callHandler) {
|
|
79
|
+
this.callHandler(callId, transport);
|
|
80
|
+
}
|
|
81
|
+
}
|
|
82
|
+
catch (error) {
|
|
83
|
+
console.error('[TwilioServer] Error handling message:', {
|
|
84
|
+
error: error instanceof Error ? error.message : String(error),
|
|
85
|
+
callId,
|
|
86
|
+
});
|
|
87
|
+
}
|
|
88
|
+
});
|
|
89
|
+
// Handle disconnect
|
|
90
|
+
ws.on('close', async () => {
|
|
91
|
+
console.log(`[TwilioServer] Connection closed: ${callId}`);
|
|
92
|
+
await this.sessionManager.closeSession(callId).catch((err) => {
|
|
93
|
+
console.error('[TwilioServer] Error closing session:', {
|
|
94
|
+
error: err instanceof Error ? err.message : String(err),
|
|
95
|
+
callId,
|
|
96
|
+
});
|
|
97
|
+
});
|
|
98
|
+
await transport.close();
|
|
99
|
+
this.transports.delete(callId);
|
|
100
|
+
});
|
|
101
|
+
// Handle errors
|
|
102
|
+
ws.on('error', (error) => {
|
|
103
|
+
console.error('[TwilioServer] WebSocket error:', {
|
|
104
|
+
error: error.message,
|
|
105
|
+
callId,
|
|
106
|
+
});
|
|
107
|
+
});
|
|
108
|
+
});
|
|
109
|
+
return new Promise((resolve) => {
|
|
110
|
+
this.wss.on('listening', () => {
|
|
111
|
+
resolve();
|
|
112
|
+
});
|
|
113
|
+
});
|
|
114
|
+
}
|
|
115
|
+
get sessions() {
|
|
116
|
+
return this.sessionManager;
|
|
117
|
+
}
|
|
118
|
+
async close() {
|
|
119
|
+
await this.sessionManager.closeAll();
|
|
120
|
+
if (this.wss) {
|
|
121
|
+
this.wss.close();
|
|
122
|
+
}
|
|
123
|
+
}
|
|
124
|
+
}
|
|
125
|
+
//# sourceMappingURL=server.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"server.js","sourceRoot":"","sources":["../src/server.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,32 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Text output for Twilio Media Streams.
|
|
3
|
+
*
|
|
4
|
+
* Sends transcribed agent responses as text events to Twilio.
|
|
5
|
+
*/
|
|
6
|
+
import { TextOutput, type TimedString } from '@ariaflow/livekit-plugin';
|
|
7
|
+
/**
|
|
8
|
+
* Sends agent text responses to Twilio Media Streams.
|
|
9
|
+
*
|
|
10
|
+
* Since Twilio Media Streams is primarily audio-focused, this
|
|
11
|
+
* implementation stores text for potential use in marks/metadata.
|
|
12
|
+
*/
|
|
13
|
+
export declare class TwilioTextOutput extends TextOutput {
|
|
14
|
+
private closed;
|
|
15
|
+
private textBuffer;
|
|
16
|
+
private sendCallback;
|
|
17
|
+
constructor(nextInChain?: TextOutput);
|
|
18
|
+
/**
|
|
19
|
+
* Set the callback for sending marks to Twilio.
|
|
20
|
+
*
|
|
21
|
+
* @param callback - Function that sends mark name to WebSocket
|
|
22
|
+
*/
|
|
23
|
+
setSendCallback(callback: (markName: string) => void): void;
|
|
24
|
+
captureText(text: string | TimedString): Promise<void>;
|
|
25
|
+
flush(): void;
|
|
26
|
+
close(): Promise<void>;
|
|
27
|
+
/**
|
|
28
|
+
* Get buffered text (for debugging or logging).
|
|
29
|
+
*/
|
|
30
|
+
getBufferedText(): string[];
|
|
31
|
+
}
|
|
32
|
+
//# sourceMappingURL=text_output.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"text_output.d.ts","sourceRoot":"","sources":["../src/text_output.ts"],"names":[],"mappings":"AAAA;;;;GAIG;AACH,OAAO,EAAE,UAAU,EAAiB,KAAK,WAAW,EAAE,MAAM,0BAA0B,CAAC;AAEvF;;;;;GAKG;AACH,qBAAa,gBAAiB,SAAQ,UAAU;IAC9C,OAAO,CAAC,MAAM,CAAkB;IAChC,OAAO,CAAC,UAAU,CAAgB;IAGlC,OAAO,CAAC,YAAY,CAAwC;gBAEhD,WAAW,CAAC,EAAE,UAAU;IAIpC;;;;OAIG;IACH,eAAe,CAAC,QAAQ,EAAE,CAAC,QAAQ,EAAE,MAAM,KAAK,IAAI,GAAG,IAAI;IAIrD,WAAW,CAAC,IAAI,EAAE,MAAM,GAAG,WAAW,GAAG,OAAO,CAAC,IAAI,CAAC;IAwB5D,KAAK,IAAI,IAAI;IAWP,KAAK,IAAI,OAAO,CAAC,IAAI,CAAC;IAK5B;;OAEG;IACH,eAAe,IAAI,MAAM,EAAE;CAG5B"}
|
|
@@ -0,0 +1,72 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Text output for Twilio Media Streams.
|
|
3
|
+
*
|
|
4
|
+
* Sends transcribed agent responses as text events to Twilio.
|
|
5
|
+
*/
|
|
6
|
+
import { TextOutput, isTimedString } from '@ariaflow/livekit-plugin';
|
|
7
|
+
/**
|
|
8
|
+
* Sends agent text responses to Twilio Media Streams.
|
|
9
|
+
*
|
|
10
|
+
* Since Twilio Media Streams is primarily audio-focused, this
|
|
11
|
+
* implementation stores text for potential use in marks/metadata.
|
|
12
|
+
*/
|
|
13
|
+
export class TwilioTextOutput extends TextOutput {
|
|
14
|
+
closed = false;
|
|
15
|
+
textBuffer = [];
|
|
16
|
+
// Callback to send marks to Twilio
|
|
17
|
+
sendCallback = () => { };
|
|
18
|
+
constructor(nextInChain) {
|
|
19
|
+
super(nextInChain);
|
|
20
|
+
}
|
|
21
|
+
/**
|
|
22
|
+
* Set the callback for sending marks to Twilio.
|
|
23
|
+
*
|
|
24
|
+
* @param callback - Function that sends mark name to WebSocket
|
|
25
|
+
*/
|
|
26
|
+
setSendCallback(callback) {
|
|
27
|
+
this.sendCallback = callback;
|
|
28
|
+
}
|
|
29
|
+
async captureText(text) {
|
|
30
|
+
if (this.closed)
|
|
31
|
+
return;
|
|
32
|
+
const textContent = isTimedString(text) ? text.text : text;
|
|
33
|
+
this.textBuffer.push(textContent);
|
|
34
|
+
// Send as a mark event to Twilio (metadata only)
|
|
35
|
+
try {
|
|
36
|
+
this.sendCallback(`agent_response_${this.textBuffer.length}`);
|
|
37
|
+
}
|
|
38
|
+
catch (error) {
|
|
39
|
+
console.error('[TwilioTextOutput] Error sending mark:', {
|
|
40
|
+
error: error instanceof Error ? error.message : String(error),
|
|
41
|
+
textLength: textContent.length,
|
|
42
|
+
timestamp: new Date().toISOString(),
|
|
43
|
+
});
|
|
44
|
+
}
|
|
45
|
+
// Access protected nextInChain property
|
|
46
|
+
const next = this.nextInChain;
|
|
47
|
+
if (next) {
|
|
48
|
+
await next.captureText(text);
|
|
49
|
+
}
|
|
50
|
+
}
|
|
51
|
+
flush() {
|
|
52
|
+
if (this.closed)
|
|
53
|
+
return;
|
|
54
|
+
this.textBuffer = [];
|
|
55
|
+
// Access protected nextInChain property
|
|
56
|
+
const next = this.nextInChain;
|
|
57
|
+
if (next) {
|
|
58
|
+
next.flush();
|
|
59
|
+
}
|
|
60
|
+
}
|
|
61
|
+
async close() {
|
|
62
|
+
this.closed = true;
|
|
63
|
+
this.textBuffer = [];
|
|
64
|
+
}
|
|
65
|
+
/**
|
|
66
|
+
* Get buffered text (for debugging or logging).
|
|
67
|
+
*/
|
|
68
|
+
getBufferedText() {
|
|
69
|
+
return [...this.textBuffer];
|
|
70
|
+
}
|
|
71
|
+
}
|
|
72
|
+
//# sourceMappingURL=text_output.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"text_output.js","sourceRoot":"","sources":["../src/text_output.ts"],"names":[],"mappings":"AAAA;;;;GAIG;AACH,OAAO,EAAE,UAAU,EAAE,aAAa,EAAoB,MAAM,0BAA0B,CAAC;AAEvF;;;;;GAKG;AACH,MAAM,OAAO,gBAAiB,SAAQ,UAAU;IACtC,MAAM,GAAY,KAAK,CAAC;IACxB,UAAU,GAAa,EAAE,CAAC;IAElC,mCAAmC;IAC3B,YAAY,GAA+B,GAAG,EAAE,GAAE,CAAC,CAAC;IAE5D,YAAY,WAAwB;QAClC,KAAK,CAAC,WAAW,CAAC,CAAC;IACrB,CAAC;IAED;;;;OAIG;IACH,eAAe,CAAC,QAAoC;QAClD,IAAI,CAAC,YAAY,GAAG,QAAQ,CAAC;IAC/B,CAAC;IAED,KAAK,CAAC,WAAW,CAAC,IAA0B;QAC1C,IAAI,IAAI,CAAC,MAAM;YAAE,OAAO;QAExB,MAAM,WAAW,GAAG,aAAa,CAAC,IAAI,CAAC,CAAC,CAAC,CAAC,IAAI,CAAC,IAAI,CAAC,CAAC,CAAC,IAAI,CAAC;QAC3D,IAAI,CAAC,UAAU,CAAC,IAAI,CAAC,WAAW,CAAC,CAAC;QAElC,iDAAiD;QACjD,IAAI,CAAC;YACH,IAAI,CAAC,YAAY,CAAC,kBAAkB,IAAI,CAAC,UAAU,CAAC,MAAM,EAAE,CAAC,CAAC;QAChE,CAAC;QAAC,OAAO,KAAK,EAAE,CAAC;YACf,OAAO,CAAC,KAAK,CAAC,wCAAwC,EAAE;gBACtD,KAAK,EAAE,KAAK,YAAY,KAAK,CAAC,CAAC,CAAC,KAAK,CAAC,OAAO,CAAC,CAAC,CAAC,MAAM,CAAC,KAAK,CAAC;gBAC7D,UAAU,EAAE,WAAW,CAAC,MAAM;gBAC9B,SAAS,EAAE,IAAI,IAAI,EAAE,CAAC,WAAW,EAAE;aACpC,CAAC,CAAC;QACL,CAAC;QAED,wCAAwC;QACxC,MAAM,IAAI,GAAI,IAAY,CAAC,WAAW,CAAC;QACvC,IAAI,IAAI,EAAE,CAAC;YACT,MAAM,IAAI,CAAC,WAAW,CAAC,IAAI,CAAC,CAAC;QAC/B,CAAC;IACH,CAAC;IAED,KAAK;QACH,IAAI,IAAI,CAAC,MAAM;YAAE,OAAO;QACxB,IAAI,CAAC,UAAU,GAAG,EAAE,CAAC;QAErB,wCAAwC;QACxC,MAAM,IAAI,GAAI,IAAY,CAAC,WAAW,CAAC;QACvC,IAAI,IAAI,EAAE,CAAC;YACT,IAAI,CAAC,KAAK,EAAE,CAAC;QACf,CAAC;IACH,CAAC;IAED,KAAK,CAAC,KAAK;QACT,IAAI,CAAC,MAAM,GAAG,IAAI,CAAC;QACnB,IAAI,CAAC,UAAU,GAAG,EAAE,CAAC;IACvB,CAAC;IAED;;OAEG;IACH,eAAe;QACb,OAAO,CAAC,GAAG,IAAI,CAAC,UAAU,CAAC,CAAC;IAC9B,CAAC;CACF"}
|