1+ // SPDX-FileCopyrightText: 2025 LiveKit, Inc.
2+ //
3+ // SPDX-License-Identifier: Apache-2.0
14import { AudioFrame } from '@livekit/rtc-node' ;
25import { EventEmitter } from 'node:events' ;
36import { createRequire } from 'node:module' ;
47import process from 'node:process' ;
58import readline from 'node:readline' ;
6- import { setInterval as setIntervalSafe , clearInterval as clearIntervalSafe } from 'node:timers' ;
9+ import { ReadableStream } from 'node:stream/web' ;
10+ import { clearInterval as clearIntervalSafe , setInterval as setIntervalSafe } from 'node:timers' ;
711import { log } from '../log.js' ;
812import { AsyncIterableQueue } from '../utils.js' ;
9- import type { Agent } from './agent.js' ;
1013import type { AgentSession } from './agent_session.js' ;
11- import { AudioInput , AudioOutput , TextOutput , type PlaybackFinishedEvent } from './io.js' ;
14+ import { AudioInput , AudioOutput , type PlaybackFinishedEvent , TextOutput } from './io.js' ;
1215import { TranscriptionSynchronizer } from './transcription/synchronizer.js' ;
13- import { ReadableStream } from 'node:stream/web' ;
1416
15- const require = createRequire ( import . meta. url ) ;
17+ const _require = createRequire ( import . meta. url ) ;
1618
1719const MAX_AUDIO_BAR = 30 ;
1820const INPUT_DB_MIN = - 70.0 ;
@@ -44,7 +46,17 @@ class ConsoleAudioInput extends AudioInput {
4446 microDb : number = INPUT_DB_MIN ;
4547 receivedAudio : boolean = false ;
4648
47- constructor ( { sampleRate = 24000 , numChannels = 1 , framesPerBuffer = 240 , deviceId } : { sampleRate ?: number ; numChannels ?: number ; framesPerBuffer ?: number ; deviceId ?: number } = { } ) {
49+ constructor ( {
50+ sampleRate = 24000 ,
51+ numChannels = 1 ,
52+ framesPerBuffer = 240 ,
53+ deviceId,
54+ } : {
55+ sampleRate ?: number ;
56+ numChannels ?: number ;
57+ framesPerBuffer ?: number ;
58+ deviceId ?: number ;
59+ } = { } ) {
4860 super ( ) ;
4961 this . sampleRate = sampleRate ;
5062 this . numChannels = numChannels ;
@@ -53,7 +65,6 @@ class ConsoleAudioInput extends AudioInput {
5365 }
5466
5567 async onAttached ( ) : Promise < void > {
56-
5768 if ( ! this . sourceSet ) {
5869 const stream = new ReadableStream < AudioFrame > ( {
5970 start : async ( controller ) => {
@@ -91,7 +102,7 @@ class ConsoleAudioInput extends AudioInput {
91102 try {
92103 // Try to use our native audio implementation
93104 const { AudioIO, SampleFormat16Bit } = await import ( './native_audio.js' ) ;
94-
105+
95106 this . ai = new AudioIO ( {
96107 inOptions : {
97108 channelCount : this . numChannels ,
@@ -134,19 +145,19 @@ class ConsoleAudioInput extends AudioInput {
134145 } catch ( error ) {
135146 // Fallback to mock audio
136147 this . logger . warn ( 'Native audio failed, using mock audio input' ) ;
137-
148+
138149 const frameSize = this . framesPerBuffer ;
139150 const intervalMs = ( frameSize / this . sampleRate ) * 1000 ;
140-
151+
141152 this . mockInterval = setInterval ( ( ) => {
142153 const silentData = new Int16Array ( frameSize * this . numChannels ) ;
143154 const frame = new AudioFrame ( silentData , this . sampleRate , this . numChannels , frameSize ) ;
144-
155+
145156 this . microDb = INPUT_DB_MIN + Math . random ( ) * 10 ;
146157 this . receivedAudio = true ;
147158 this . queue . put ( frame ) ;
148159 } , intervalMs ) ;
149-
160+
150161 this . started = true ;
151162 }
152163 }
@@ -212,46 +223,49 @@ class ConsoleAudioOutput extends AudioOutput {
212223 private dispatchTimer : NodeJS . Timeout | null = null ;
213224 private _logger = log ( ) ;
214225
215- constructor ( { sampleRate = 24000 , numChannels = 1 } : { sampleRate ?: number ; numChannels ?: number } = { } ) {
226+ constructor ( {
227+ sampleRate = 24000 ,
228+ numChannels = 1 ,
229+ } : { sampleRate ?: number ; numChannels ?: number } = { } ) {
216230 super ( sampleRate ) ;
217231 this . outputSampleRate = sampleRate ;
218232 this . numChannels = numChannels ;
219233 }
220234
221235 async onAttached ( ) : Promise < void > {
222236 if ( this . started ) return ;
223-
237+
224238 try {
225239 // Try to use our native audio implementation
226240 const { AudioIO } = await import ( './native_audio.js' ) ;
227-
241+
228242 this . ao = new AudioIO ( {
229243 inOptions : undefined , // output only
230244 outOptions : {
231245 channelCount : this . numChannels ,
232246 sampleRate : this . outputSampleRate ,
233247 } ,
234248 } ) ;
235-
249+
236250 this . ao . start ( ) ;
237251 this . started = true ;
238252 this . _logger . info ( 'Using native audio output via command-line tools' ) ;
239253 } catch ( error ) {
240254 // Fallback to mock audio output
241255 this . _logger . warn ( 'Native audio failed, using mock audio output' , error ) ;
242-
256+
243257 this . ao = {
244258 write : ( data : Buffer ) => {
245259 const frameCount = data . length / ( 2 * this . numChannels ) ;
246260 const durationMs = ( frameCount / this . outputSampleRate ) * 1000 ;
247-
261+
248262 setTimeout ( ( ) => {
249263 this . emit ( 'playbackFinished' ) ;
250264 } , durationMs ) ;
251265 } ,
252266 end : ( ) => { } ,
253267 } ;
254-
268+
255269 this . started = true ;
256270 }
257271 }
@@ -294,7 +308,10 @@ class ConsoleAudioOutput extends AudioOutput {
294308 this . dispatchTimer = null ;
295309 }
296310 const played = Math . min ( ( Date . now ( ) - this . captureStart ) / 1000 , this . pushedDuration ) ;
297- this . onPlaybackFinished ( { playbackPosition : played , interrupted : played + 1.0 < this . pushedDuration } ) ;
311+ this . onPlaybackFinished ( {
312+ playbackPosition : played ,
313+ interrupted : played + 1.0 < this . pushedDuration ,
314+ } ) ;
298315 this . pushedDuration = 0 ;
299316 this . captureStart = 0 ;
300317 }
@@ -322,14 +339,16 @@ export class ChatCLI extends EventEmitter {
322339 private currentAudioLine : string = '' ;
323340 private isLogging : boolean = false ;
324341
325-
326- constructor ( agentSession : AgentSession , { syncTranscription = true } : { syncTranscription ?: boolean } = { } ) {
342+ constructor (
343+ agentSession : AgentSession ,
344+ { syncTranscription = true } : { syncTranscription ?: boolean } = { } ,
345+ ) {
327346 super ( ) ;
328347 this . session = agentSession ;
329348 this . textSink = new StdoutTextOutput ( ) ;
330349 this . audioSink = new ConsoleAudioOutput ( ) ;
331350 this . inputAudio = new ConsoleAudioInput ( ) ;
332-
351+
333352 if ( syncTranscription ) {
334353 this . transcriptSyncer = new TranscriptionSynchronizer ( this . audioSink , this . textSink ) ;
335354 }
@@ -450,15 +469,19 @@ export class ChatCLI extends EventEmitter {
450469
451470 private updateSpeaker ( enable : boolean ) {
452471 if ( enable ) {
453- this . session . output . audio = this . transcriptSyncer ? this . transcriptSyncer . audioOutput : this . audioSink ;
472+ this . session . output . audio = this . transcriptSyncer
473+ ? this . transcriptSyncer . audioOutput
474+ : this . audioSink ;
454475 } else {
455476 this . session . output . audio = null ;
456477 }
457478 }
458479
459480 private updateTextOutput ( { enable, stdoutEnable } : { enable : boolean ; stdoutEnable : boolean } ) {
460481 if ( enable ) {
461- this . session . output . transcription = this . transcriptSyncer ? this . transcriptSyncer . textOutput : this . textSink ;
482+ this . session . output . transcription = this . transcriptSyncer
483+ ? this . transcriptSyncer . textOutput
484+ : this . textSink ;
462485 this . textSink . setEnabled ( stdoutEnable ) ;
463486 } else {
464487 this . session . output . transcription = null ;
0 commit comments