@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.
@@ -0,0 +1,95 @@
1
+ /**
2
+ * Twilio Media Streams Transport Adapter.
3
+ *
4
+ * Connects AriaFlow agents to Twilio Media Streams for telephony
5
+ * voice AI applications. Works in Cloudflare Workers, Node.js, and
6
+ * any runtime with WebSocket support.
7
+ *
8
+ * Twilio sends G.711 μ-law audio at 8kHz. This transport handles
9
+ * decoding, resampling to 24kHz, and integration with AriaFlow sessions.
10
+ */
11
+ import type { TransportAdapterConfig } from '@ariaflow/livekit-plugin';
12
+ import { TransportAdapter } from '@ariaflow/livekit-plugin';
13
+ import { TwilioAudioInput } from './audio_input.js';
14
+ import { TwilioAudioOutput } from './audio_output.js';
15
+ import { TwilioTextOutput } from './text_output.js';
16
+ /**
17
+ * Options for creating a Twilio transport adapter.
18
+ */
19
+ export interface TwilioTransportOptions {
20
+ id?: string;
21
+ /**
22
+ * Callback to send messages to the WebSocket.
23
+ * The transport will format messages as Twilio Media Streams events.
24
+ */
25
+ send: (message: string) => void;
26
+ }
27
+ /**
28
+ * Transport adapter for Twilio Media Streams.
29
+ *
30
+ * This adapter bridges Twilio's Media Streams WebSocket protocol
31
+ * with AriaFlow's voice agent pipeline.
32
+ *
33
+ * Audio flow:
34
+ * - Inbound: Twilio → μ-law 8kHz → decode → resample to 24kHz → AriaFlow STT
35
+ * - Outbound: AriaFlow TTS → 24kHz → resample to 8kHz → μ-law encode → Twilio
36
+ *
37
+ * Usage in Cloudflare Workers:
38
+ * ```typescript
39
+ * export default {
40
+ * async fetch(request, env) {
41
+ * if (request.headers.get('Upgrade') === 'websocket') {
42
+ * const pair = new WebSocketPair();
43
+ * const [client, server] = Object.values(pair);
44
+ *
45
+ * const transport = new TwilioTransportAdapter({
46
+ * send: (msg) => server.send(msg),
47
+ * });
48
+ *
49
+ * server.accept();
50
+ *
51
+ * // Handle incoming messages
52
+ * server.addEventListener('message', (event) => {
53
+ * const data = JSON.parse(event.data);
54
+ * if (data.event === 'media') {
55
+ * transport.audioInput.handleMediaEvent(data);
56
+ * }
57
+ * });
58
+ *
59
+ * const { agent, sessionOptions } = createAriaFlowSession({...});
60
+ * await sessionManager.startSession(transport, agent, sessionOptions);
61
+ *
62
+ * return new Response(null, { status: 101, webSocket: client });
63
+ * }
64
+ * }
65
+ * }
66
+ * ```
67
+ */
68
+ export declare class TwilioTransportAdapter extends TransportAdapter {
69
+ readonly id: string;
70
+ readonly audioInput: TwilioAudioInput;
71
+ readonly audioOutput: TwilioAudioOutput;
72
+ readonly textOutput: TwilioTextOutput;
73
+ readonly config: TransportAdapterConfig;
74
+ private _isOpen;
75
+ private _streamSid;
76
+ constructor(options: TwilioTransportOptions);
77
+ /**
78
+ * Get the current Twilio stream SID.
79
+ */
80
+ get streamSid(): string;
81
+ get isOpen(): boolean;
82
+ close(): Promise<void>;
83
+ /**
84
+ * Handle an incoming message from Twilio.
85
+ * Call this for each WebSocket message received from Twilio.
86
+ *
87
+ * @param message - JSON string from Twilio Media Streams
88
+ */
89
+ handleMessage(message: string): void;
90
+ /**
91
+ * Send a clear event to Twilio (clears audio buffer).
92
+ */
93
+ clearAudio(): void;
94
+ }
95
+ //# sourceMappingURL=transport_adapter.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"transport_adapter.d.ts","sourceRoot":"","sources":["../src/transport_adapter.ts"],"names":[],"mappings":"AAAA;;;;;;;;;GASG;AAEH,OAAO,KAAK,EAEV,sBAAsB,EACvB,MAAM,0BAA0B,CAAC;AAClC,OAAO,EAAE,gBAAgB,EAAE,MAAM,0BAA0B,CAAC;AAC5D,OAAO,EAAE,gBAAgB,EAAE,MAAM,kBAAkB,CAAC;AACpD,OAAO,EAAE,iBAAiB,EAAE,MAAM,mBAAmB,CAAC;AACtD,OAAO,EAAE,gBAAgB,EAAE,MAAM,kBAAkB,CAAC;AAEpD;;GAEG;AACH,MAAM,WAAW,sBAAsB;IACrC,EAAE,CAAC,EAAE,MAAM,CAAC;IACZ;;;OAGG;IACH,IAAI,EAAE,CAAC,OAAO,EAAE,MAAM,KAAK,IAAI,CAAC;CACjC;AAED;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GAwCG;AACH,qBAAa,sBAAuB,SAAQ,gBAAgB;IAC1D,QAAQ,CAAC,EAAE,EAAE,MAAM,CAAC;IACpB,QAAQ,CAAC,UAAU,EAAE,gBAAgB,CAAC;IACtC,QAAQ,CAAC,WAAW,EAAE,iBAAiB,CAAC;IACxC,QAAQ,CAAC,UAAU,EAAE,gBAAgB,CAAC;IACtC,QAAQ,CAAC,MAAM,EAAE,sBAAsB,CAAC;IAExC,OAAO,CAAC,OAAO,CAAiB;IAChC,OAAO,CAAC,UAAU,CAAc;gBAEpB,OAAO,EAAE,sBAAsB;IAkC3C;;OAEG;IACH,IAAI,SAAS,IAAI,MAAM,CAEtB;IAED,IAAI,MAAM,IAAI,OAAO,CAEpB;IAEK,KAAK,IAAI,OAAO,CAAC,IAAI,CAAC;IAS5B;;;;;OAKG;IACH,aAAa,CAAC,OAAO,EAAE,MAAM,GAAG,IAAI;IAyDpC;;OAEG;IACH,UAAU,IAAI,IAAI;CAGnB"}
@@ -0,0 +1,174 @@
1
+ /**
2
+ * Twilio Media Streams Transport Adapter.
3
+ *
4
+ * Connects AriaFlow agents to Twilio Media Streams for telephony
5
+ * voice AI applications. Works in Cloudflare Workers, Node.js, and
6
+ * any runtime with WebSocket support.
7
+ *
8
+ * Twilio sends G.711 μ-law audio at 8kHz. This transport handles
9
+ * decoding, resampling to 24kHz, and integration with AriaFlow sessions.
10
+ */
11
+ import { TransportAdapter } from '@ariaflow/livekit-plugin';
12
+ import { TwilioAudioInput } from './audio_input.js';
13
+ import { TwilioAudioOutput } from './audio_output.js';
14
+ import { TwilioTextOutput } from './text_output.js';
15
+ /**
16
+ * Transport adapter for Twilio Media Streams.
17
+ *
18
+ * This adapter bridges Twilio's Media Streams WebSocket protocol
19
+ * with AriaFlow's voice agent pipeline.
20
+ *
21
+ * Audio flow:
22
+ * - Inbound: Twilio → μ-law 8kHz → decode → resample to 24kHz → AriaFlow STT
23
+ * - Outbound: AriaFlow TTS → 24kHz → resample to 8kHz → μ-law encode → Twilio
24
+ *
25
+ * Usage in Cloudflare Workers:
26
+ * ```typescript
27
+ * export default {
28
+ * async fetch(request, env) {
29
+ * if (request.headers.get('Upgrade') === 'websocket') {
30
+ * const pair = new WebSocketPair();
31
+ * const [client, server] = Object.values(pair);
32
+ *
33
+ * const transport = new TwilioTransportAdapter({
34
+ * send: (msg) => server.send(msg),
35
+ * });
36
+ *
37
+ * server.accept();
38
+ *
39
+ * // Handle incoming messages
40
+ * server.addEventListener('message', (event) => {
41
+ * const data = JSON.parse(event.data);
42
+ * if (data.event === 'media') {
43
+ * transport.audioInput.handleMediaEvent(data);
44
+ * }
45
+ * });
46
+ *
47
+ * const { agent, sessionOptions } = createAriaFlowSession({...});
48
+ * await sessionManager.startSession(transport, agent, sessionOptions);
49
+ *
50
+ * return new Response(null, { status: 101, webSocket: client });
51
+ * }
52
+ * }
53
+ * }
54
+ * ```
55
+ */
56
+ export class TwilioTransportAdapter extends TransportAdapter {
57
+ id;
58
+ audioInput;
59
+ audioOutput;
60
+ textOutput;
61
+ config;
62
+ _isOpen = true;
63
+ _streamSid = '';
64
+ constructor(options) {
65
+ super();
66
+ this.id = options.id || `twilio-${crypto.randomUUID()}`;
67
+ // Twilio uses G.711 μ-law at 8kHz, but we resample to 24kHz internally
68
+ this.config = {
69
+ sampleRate: 24000, // Internal sample rate after resampling
70
+ numChannels: 1,
71
+ encoding: 'pcm_s16le',
72
+ samplesPerChannel: 2400, // 100ms at 24kHz
73
+ };
74
+ // Create I/O implementations
75
+ this.audioInput = new TwilioAudioInput();
76
+ this.audioOutput = new TwilioAudioOutput();
77
+ this.textOutput = new TwilioTextOutput();
78
+ // Wire up send callbacks with streamSid tracking
79
+ this.audioOutput.setSendCallback((message) => {
80
+ // Inject current streamSid into media events
81
+ const event = JSON.parse(message);
82
+ event.streamSid = this._streamSid;
83
+ options.send(JSON.stringify(event));
84
+ });
85
+ this.textOutput.setSendCallback((markName) => {
86
+ options.send(JSON.stringify({
87
+ event: 'mark',
88
+ streamSid: this._streamSid,
89
+ sequenceNumber: `${Date.now()}`,
90
+ mark: { name: markName },
91
+ }));
92
+ });
93
+ }
94
+ /**
95
+ * Get the current Twilio stream SID.
96
+ */
97
+ get streamSid() {
98
+ return this._streamSid;
99
+ }
100
+ get isOpen() {
101
+ return this._isOpen;
102
+ }
103
+ async close() {
104
+ if (!this._isOpen)
105
+ return;
106
+ this._isOpen = false;
107
+ await this.audioInput.close();
108
+ await this.audioOutput.close();
109
+ await this.textOutput.close();
110
+ }
111
+ /**
112
+ * Handle an incoming message from Twilio.
113
+ * Call this for each WebSocket message received from Twilio.
114
+ *
115
+ * @param message - JSON string from Twilio Media Streams
116
+ */
117
+ handleMessage(message) {
118
+ try {
119
+ const event = JSON.parse(message);
120
+ switch (event.event) {
121
+ case 'media':
122
+ // Audio data
123
+ this.audioInput.handleMediaEvent(event);
124
+ break;
125
+ case 'connected':
126
+ // Connection established
127
+ console.log('[TwilioTransport] Connected to Twilio Media Streams');
128
+ break;
129
+ case 'start':
130
+ // Stream established - capture streamSid and ready to send audio
131
+ this._streamSid = event.start?.streamSid || event.streamSid || '';
132
+ console.log('[TwilioTransport] Stream started:', {
133
+ streamSid: this._streamSid,
134
+ callSid: event.start?.callSid,
135
+ tracks: event.start?.tracks,
136
+ });
137
+ break;
138
+ case 'stop':
139
+ case 'disconnected':
140
+ // Stream ended - flush remaining audio
141
+ console.log('[TwilioTransport] Stream ended:', this._streamSid);
142
+ this.audioInput.endCurrentStream();
143
+ this._isOpen = false;
144
+ this._streamSid = '';
145
+ break;
146
+ case 'clear':
147
+ // Clear audio buffer
148
+ console.log('[TwilioTransport] Clear audio buffer requested');
149
+ this.audioOutput.clearBuffer();
150
+ break;
151
+ case 'mark':
152
+ // Metadata mark from Twilio
153
+ console.log('[TwilioTransport] Received mark:', event.mark?.name);
154
+ break;
155
+ default:
156
+ console.log('[TwilioTransport] Unknown event:', event.event);
157
+ }
158
+ }
159
+ catch (error) {
160
+ console.error('[TwilioTransport] Error handling message:', {
161
+ error: error instanceof Error ? error.message : String(error),
162
+ transportId: this.id,
163
+ timestamp: new Date().toISOString(),
164
+ });
165
+ }
166
+ }
167
+ /**
168
+ * Send a clear event to Twilio (clears audio buffer).
169
+ */
170
+ clearAudio() {
171
+ this.audioOutput.clearBuffer();
172
+ }
173
+ }
174
+ //# sourceMappingURL=transport_adapter.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"transport_adapter.js","sourceRoot":"","sources":["../src/transport_adapter.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,130 @@
1
+ /**
2
+ * Twilio Media Streams protocol message types and handlers.
3
+ *
4
+ * Twilio's Media Streams API sends JSON events over WebSocket:
5
+ * - Media events: Audio data with base64 payload
6
+ * - Control events: connected, disconnected, started, stopped
7
+ * - Metadata: marks/custom events
8
+ *
9
+ * Reference: https://www.twilio.com/docs/voice/media-streams
10
+ */
11
+ /**
12
+ * Twilio Media Streams event types.
13
+ */
14
+ export type TwilioEventType = 'connected' | 'start' | 'media' | 'stop' | 'mark' | 'clear';
15
+ /**
16
+ * Base interface for all Twilio events.
17
+ */
18
+ export interface TwilioEvent {
19
+ event: TwilioEventType;
20
+ sequenceNumber?: string;
21
+ media?: {
22
+ track: string;
23
+ chunk: string;
24
+ timestamp: string;
25
+ payload: string;
26
+ };
27
+ start?: {
28
+ streamSid: string;
29
+ callSid: string;
30
+ tracks?: string[];
31
+ mediaFormat?: {
32
+ encoding: 'audio/x-mulaw' | 'mulaw';
33
+ sampleRate: 8000;
34
+ channels: 1;
35
+ };
36
+ };
37
+ mark?: {
38
+ name: string;
39
+ };
40
+ streamSid?: string;
41
+ }
42
+ /**
43
+ * Media event with audio data.
44
+ */
45
+ export interface TwilioMediaEvent extends TwilioEvent {
46
+ event: 'media';
47
+ media: {
48
+ track: string;
49
+ chunk: string;
50
+ timestamp: string;
51
+ payload: string;
52
+ };
53
+ }
54
+ /**
55
+ * Connected event - stream established.
56
+ */
57
+ export interface TwilioConnectedEvent extends TwilioEvent {
58
+ event: 'connected';
59
+ streamSid: string;
60
+ callSid: string;
61
+ }
62
+ /**
63
+ * Started event - media transmission began.
64
+ */
65
+ export interface TwilioStartEvent extends TwilioEvent {
66
+ event: 'start';
67
+ start: {
68
+ streamSid: string;
69
+ callSid: string;
70
+ tracks?: string[];
71
+ mediaFormat?: {
72
+ encoding: 'audio/x-mulaw' | 'mulaw';
73
+ sampleRate: 8000;
74
+ channels: 1;
75
+ };
76
+ };
77
+ streamSid?: string;
78
+ }
79
+ /**
80
+ * Stopped event - media transmission ended.
81
+ */
82
+ export interface TwilioStopEvent extends TwilioEvent {
83
+ event: 'stop';
84
+ streamSid: string;
85
+ callSid: string;
86
+ }
87
+ /**
88
+ * Mark event - custom metadata from TwiML.
89
+ */
90
+ export interface TwilioMarkEvent extends TwilioEvent {
91
+ event: 'mark';
92
+ streamSid: string;
93
+ mark: {
94
+ name: string;
95
+ };
96
+ }
97
+ /**
98
+ * Clear event - clears buffered audio.
99
+ */
100
+ export interface TwilioClearEvent extends TwilioEvent {
101
+ event: 'clear';
102
+ streamSid: string;
103
+ }
104
+ /**
105
+ * Parse a JSON message from Twilio Media Streams.
106
+ *
107
+ * @param message - Raw JSON string from WebSocket
108
+ * @returns Parsed Twilio event or null if invalid
109
+ */
110
+ export declare function parseTwilioMessage(message: string): TwilioEvent | null;
111
+ /**
112
+ * Check if an event is a media event with audio data.
113
+ */
114
+ export declare function isMediaEvent(event: TwilioEvent): event is TwilioMediaEvent;
115
+ /**
116
+ * Extract μ-law audio payload from a media event.
117
+ *
118
+ * @param event - Twilio media event
119
+ * @returns Base64-encoded μ-law audio data or null
120
+ */
121
+ export declare function extractMediaPayload(event: TwilioMediaEvent): string | null;
122
+ /**
123
+ * Create a clear message to send to Twilio (clears audio buffer).
124
+ */
125
+ export declare function createClearMessage(): string;
126
+ /**
127
+ * Create a mark message (adds metadata to the stream).
128
+ */
129
+ export declare function createMarkMessage(name: string): string;
130
+ //# sourceMappingURL=twilio_protocol.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"twilio_protocol.d.ts","sourceRoot":"","sources":["../src/twilio_protocol.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,74 @@
1
+ /**
2
+ * Twilio Media Streams protocol message types and handlers.
3
+ *
4
+ * Twilio's Media Streams API sends JSON events over WebSocket:
5
+ * - Media events: Audio data with base64 payload
6
+ * - Control events: connected, disconnected, started, stopped
7
+ * - Metadata: marks/custom events
8
+ *
9
+ * Reference: https://www.twilio.com/docs/voice/media-streams
10
+ */
11
+ /**
12
+ * Parse a JSON message from Twilio Media Streams.
13
+ *
14
+ * @param message - Raw JSON string from WebSocket
15
+ * @returns Parsed Twilio event or null if invalid
16
+ */
17
+ export function parseTwilioMessage(message) {
18
+ try {
19
+ const event = JSON.parse(message);
20
+ // Validate event has required fields
21
+ if (!event.event) {
22
+ return null;
23
+ }
24
+ // Validate event type
25
+ const validEvents = ['connected', 'start', 'media', 'stop', 'mark', 'clear'];
26
+ if (!validEvents.includes(event.event)) {
27
+ return null;
28
+ }
29
+ return event;
30
+ }
31
+ catch {
32
+ return null;
33
+ }
34
+ }
35
+ /**
36
+ * Check if an event is a media event with audio data.
37
+ */
38
+ export function isMediaEvent(event) {
39
+ return event.event === 'media' && event.media !== undefined && typeof event.media.payload === 'string';
40
+ }
41
+ /**
42
+ * Extract μ-law audio payload from a media event.
43
+ *
44
+ * @param event - Twilio media event
45
+ * @returns Base64-encoded μ-law audio data or null
46
+ */
47
+ export function extractMediaPayload(event) {
48
+ if (event.media && event.media.payload) {
49
+ return event.media.payload;
50
+ }
51
+ return null;
52
+ }
53
+ /**
54
+ * Create a clear message to send to Twilio (clears audio buffer).
55
+ */
56
+ export function createClearMessage() {
57
+ return JSON.stringify({
58
+ event: 'clear',
59
+ streamSid: '', // Will be filled by sender
60
+ sequenceNumber: `${Date.now()}`,
61
+ });
62
+ }
63
+ /**
64
+ * Create a mark message (adds metadata to the stream).
65
+ */
66
+ export function createMarkMessage(name) {
67
+ return JSON.stringify({
68
+ event: 'mark',
69
+ streamSid: '', // Will be filled by sender
70
+ sequenceNumber: `${Date.now()}`,
71
+ mark: { name },
72
+ });
73
+ }
74
+ //# sourceMappingURL=twilio_protocol.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"twilio_protocol.js","sourceRoot":"","sources":["../src/twilio_protocol.ts"],"names":[],"mappings":"AAAA;;;;;;;;;GASG;AA6GH;;;;;GAKG;AACH,MAAM,UAAU,kBAAkB,CAAC,OAAe;IAChD,IAAI,CAAC;QACH,MAAM,KAAK,GAAG,IAAI,CAAC,KAAK,CAAC,OAAO,CAAgB,CAAC;QAEjD,qCAAqC;QACrC,IAAI,CAAC,KAAK,CAAC,KAAK,EAAE,CAAC;YACjB,OAAO,IAAI,CAAC;QACd,CAAC;QAED,sBAAsB;QACtB,MAAM,WAAW,GAAsB,CAAC,WAAW,EAAE,OAAO,EAAE,OAAO,EAAE,MAAM,EAAE,MAAM,EAAE,OAAO,CAAC,CAAC;QAChG,IAAI,CAAC,WAAW,CAAC,QAAQ,CAAC,KAAK,CAAC,KAAK,CAAC,EAAE,CAAC;YACvC,OAAO,IAAI,CAAC;QACd,CAAC;QAED,OAAO,KAAK,CAAC;IACf,CAAC;IAAC,MAAM,CAAC;QACP,OAAO,IAAI,CAAC;IACd,CAAC;AACH,CAAC;AAED;;GAEG;AACH,MAAM,UAAU,YAAY,CAAC,KAAkB;IAC7C,OAAO,KAAK,CAAC,KAAK,KAAK,OAAO,IAAI,KAAK,CAAC,KAAK,KAAK,SAAS,IAAI,OAAO,KAAK,CAAC,KAAK,CAAC,OAAO,KAAK,QAAQ,CAAC;AACzG,CAAC;AAED;;;;;GAKG;AACH,MAAM,UAAU,mBAAmB,CAAC,KAAuB;IACzD,IAAI,KAAK,CAAC,KAAK,IAAI,KAAK,CAAC,KAAK,CAAC,OAAO,EAAE,CAAC;QACvC,OAAO,KAAK,CAAC,KAAK,CAAC,OAAO,CAAC;IAC7B,CAAC;IACD,OAAO,IAAI,CAAC;AACd,CAAC;AAED;;GAEG;AACH,MAAM,UAAU,kBAAkB;IAChC,OAAO,IAAI,CAAC,SAAS,CAAC;QACpB,KAAK,EAAE,OAAO;QACd,SAAS,EAAE,EAAE,EAAE,2BAA2B;QAC1C,cAAc,EAAE,GAAG,IAAI,CAAC,GAAG,EAAE,EAAE;KAChC,CAAC,CAAC;AACL,CAAC;AAED;;GAEG;AACH,MAAM,UAAU,iBAAiB,CAAC,IAAY;IAC5C,OAAO,IAAI,CAAC,SAAS,CAAC;QACpB,KAAK,EAAE,MAAM;QACb,SAAS,EAAE,EAAE,EAAE,2BAA2B;QAC1C,cAAc,EAAE,GAAG,IAAI,CAAC,GAAG,EAAE,EAAE;QAC/B,IAAI,EAAE,EAAE,IAAI,EAAE;KACf,CAAC,CAAC;AACL,CAAC"}
package/package.json ADDED
@@ -0,0 +1,43 @@
1
+ {
2
+ "name": "@ariaflowagents/livekit-plugin-transport-twilio",
3
+ "version": "0.9.0",
4
+ "description": "Twilio Media Streams transport adapter for AriaFlow agents. Works with Node.js, Bun, Deno, and any WebSocket-compatible runtime.",
5
+ "type": "module",
6
+ "main": "dist/index.js",
7
+ "types": "dist/index.d.ts",
8
+ "exports": {
9
+ ".": {
10
+ "types": "./dist/index.d.ts",
11
+ "import": "./dist/index.js"
12
+ }
13
+ },
14
+ "files": ["dist"],
15
+ "scripts": {
16
+ "build": "tsc",
17
+ "clean": "rm -rf dist",
18
+ "typecheck": "tsc --noEmit",
19
+ "example:echo": "tsx examples/echo_bot.ts",
20
+ "example:server": "tsx examples/standalone_server.ts",
21
+ "example:hono": "tsx examples/hono_server.ts"
22
+ },
23
+ "dependencies": {},
24
+ "peerDependencies": {
25
+ "@ariaflowagents/livekit-plugin": "workspace:*",
26
+ "@livekit/agents": ">=1.0.0"
27
+ },
28
+ "devDependencies": {
29
+ "@cloudflare/workers-types": "^4.20241218.0",
30
+ "hono": "^4.6.0",
31
+ "tsx": "^4.19.0",
32
+ "typescript": "^5.5.0",
33
+ "ws": "^8.18.0"
34
+ },
35
+ "keywords": [
36
+ "ariaflow",
37
+ "livekit",
38
+ "twilio",
39
+ "voice",
40
+ "transport"
41
+ ],
42
+ "license": "Apache-2.0"
43
+ }