@dtelecom/agents-js 0.3.2 → 0.3.3

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/index.mjs CHANGED
@@ -1002,8 +1002,9 @@ var Pipeline = class extends EventEmitter {
1002
1002
  wakeConsumer = null;
1003
1003
  continue;
1004
1004
  }
1005
+ const canPrefetch = this.tts && !this.tts.sequential;
1005
1006
  const tryPrefetch = () => {
1006
- if (state.prefetched || !this.tts) return;
1007
+ if (state.prefetched || !canPrefetch) return;
1007
1008
  if (sentenceQueue.length > 0) {
1008
1009
  const next = sentenceQueue.shift();
1009
1010
  if (/\w/.test(next)) {
@@ -1 +1 @@
1
- {"version":3,"sources":["../src/core/voice-agent.ts","../src/room/room-connection.ts","../src/room/audio-input.ts","../src/room/audio-output.ts","../src/core/pipeline.ts","../src/core/context-manager.ts","../src/core/sentence-splitter.ts","../src/core/turn-detector.ts","../src/core/barge-in.ts"],"sourcesContent":["/**\n * VoiceAgent — top-level orchestrator for AI voice agents in dTelecom rooms.\n *\n * Wires together:\n * - RoomConnection (join room, publish audio track)\n * - Pipeline (STT -> LLM -> TTS)\n * - AudioInput (per-participant audio streams)\n * - AudioOutput (agent's published audio)\n * - RoomMemory (optional persistent memory)\n */\n\nimport { EventEmitter } from 'events';\nimport type { RemoteAudioTrack, RemoteTrackPublication, RemoteParticipant } from '@dtelecom/server-sdk-node';\nimport { RoomConnection } from '../room/room-connection';\nimport { AudioInput } from '../room/audio-input';\nimport { AudioOutput } from '../room/audio-output';\nimport { Pipeline } from './pipeline';\nimport type { AgentConfig, AgentStartOptions, DataMessageHandler } from './types';\nimport { createLogger } from '../utils/logger';\n\nconst log = createLogger('VoiceAgent');\n\nexport class VoiceAgent extends EventEmitter {\n private readonly config: AgentConfig;\n private connection: RoomConnection | null = null;\n private pipeline: Pipeline | null = null;\n private audioInputs = new Map<string, AudioInput>();\n private audioOutput: AudioOutput | null = null;\n private memory: import('../memory/room-memory').RoomMemory | null = null;\n private _running = false;\n\n constructor(config: AgentConfig) {\n super();\n this.config = config;\n }\n\n get running(): boolean {\n return this._running;\n }\n\n get room() { return this.connection?.room ?? null; }\n\n /** Enable saving raw TTS audio as WAV files to `dir` for debugging. */\n enableAudioDump(dir: string): void {\n this._dumpDir = dir;\n if (this.audioOutput) {\n this.audioOutput.dumpDir = dir;\n }\n }\n private _dumpDir: string | null = null;\n\n /**\n * Speak text directly via TTS, bypassing the LLM.\n * Use for greetings or announcements. Supports barge-in.\n */\n async say(text: string): Promise<void> {\n if (!this.pipeline) {\n throw new Error('Agent not started — call start() first');\n }\n await this.pipeline.say(text);\n }\n\n /**\n * Switch STT language on all active streams.\n * Use for bilingual lessons — e.g. switch to 'es' with forceWhisper when\n * expecting Spanish, back to 'auto' for Parakeet auto-detect otherwise.\n */\n setSTTLanguage(language: string, options?: { forceWhisper?: boolean }): void {\n this.pipeline?.setSTTLanguage(language, options);\n }\n\n /** Start the agent — connect to room and begin listening. */\n async start(options: AgentStartOptions): Promise<void> {\n if (this._running) {\n throw new Error('Agent is already running');\n }\n\n log.info(`Starting agent for room \"${options.room}\"...`);\n\n // 1. Initialize memory (if enabled)\n if (this.config.memory?.enabled) {\n const { RoomMemory } = await import('../memory/room-memory');\n this.memory = new RoomMemory({\n dbPath: this.config.memory.dbPath ?? './data/memory.db',\n room: this.config.memory.scope ?? options.room,\n });\n await this.memory.init();\n this.memory.startSession();\n log.info('Memory initialized');\n }\n\n // 2. Connect to room\n this.connection = new RoomConnection();\n await this.connection.connect({\n room: options.room,\n apiKey: options.apiKey,\n apiSecret: options.apiSecret,\n token: options.token,\n wsUrl: options.wsUrl,\n identity: options.identity ?? 'agent',\n name: options.name ?? options.identity ?? 'AI Agent',\n });\n\n // 3. Publish audio track + start sending silence to keep it active\n const source = await this.connection.publishAudioTrack();\n this.audioOutput = new AudioOutput(source);\n if (this._dumpDir) this.audioOutput.dumpDir = this._dumpDir;\n this.audioOutput.startSilence();\n\n // 4. Create pipeline (warmup is handled internally by Pipeline)\n this.pipeline = new Pipeline({\n stt: this.config.stt,\n llm: this.config.llm,\n tts: this.config.tts,\n instructions: this.config.instructions,\n audioOutput: this.audioOutput,\n respondMode: this.config.respondMode,\n agentName: this.config.agentName,\n nameVariants: this.config.nameVariants,\n memory: this.memory ?? undefined,\n maxContextTokens: this.config.maxContextTokens,\n tools: this.config.tools,\n });\n\n // Forward pipeline events\n this.pipeline.on('transcription', (result) => this.emit('transcription', result));\n this.pipeline.on('sentence', (text, raw) => this.emit('sentence', text, raw));\n this.pipeline.on('response', (text) => this.emit('response', text));\n this.pipeline.on('agentState', (state) => this.emit('agentState', state));\n this.pipeline.on('toolCall', (tc) => this.emit('toolCall', tc));\n this.pipeline.on('error', (error) => this.emit('error', error));\n\n // 5. Subscribe to existing remote participants\n for (const participant of this.connection.room.remoteParticipants.values()) {\n for (const [, pub] of participant.trackPublications) {\n if (pub.track) {\n this.handleTrackSubscribed(pub.track as RemoteAudioTrack, pub as RemoteTrackPublication, participant);\n }\n }\n }\n\n // 6. Listen for new tracks\n this.connection.room.on('trackSubscribed', (track, pub, participant) => {\n this.handleTrackSubscribed(track, pub, participant);\n });\n\n this.connection.room.on('trackUnsubscribed', (track, _pub, participant) => {\n this.handleTrackUnsubscribed(track, participant);\n });\n\n this.connection.room.on('participantDisconnected', (participant) => {\n this.handleParticipantDisconnected(participant);\n });\n\n this.connection.room.on('disconnected', (reason) => {\n log.info(`Room disconnected: ${reason}`);\n this.emit('disconnected', reason);\n });\n\n // 7. Data channel support\n if (this.config.onDataMessage) {\n this.setupDataChannel(this.config.onDataMessage);\n }\n\n this._running = true;\n this.emit('connected');\n log.info('Agent started and listening');\n }\n\n /** Stop the agent — disconnect and clean up. */\n async stop(): Promise<void> {\n if (!this._running) return;\n\n log.info('Stopping agent...');\n this._running = false;\n\n if (this.pipeline) {\n await this.pipeline.stop();\n this.pipeline = null;\n }\n\n // End memory session (generates summary)\n if (this.memory) {\n try {\n await this.memory.endSession(this.config.llm);\n await this.memory.close();\n } catch (err) {\n log.error('Error closing memory:', err);\n }\n this.memory = null;\n }\n\n for (const [, input] of this.audioInputs) {\n input.close();\n }\n this.audioInputs.clear();\n\n if (this.audioOutput) {\n this.audioOutput.stop();\n this.audioOutput = null;\n }\n\n if (this.connection) {\n await this.connection.disconnect();\n this.connection = null;\n }\n\n this.emit('disconnected', 'agent_stopped');\n log.info('Agent stopped');\n }\n\n private setupDataChannel(handler: DataMessageHandler): void {\n if (!this.connection) return;\n\n this.connection.room.on('dataReceived', (payload: Uint8Array, participant?: RemoteParticipant, _kind?: unknown, topic?: string) => {\n const identity = participant?.identity ?? 'unknown';\n handler(payload, identity, topic);\n });\n\n log.info('Data channel handler registered');\n }\n\n private handleTrackSubscribed(\n track: RemoteAudioTrack,\n _publication: RemoteTrackPublication,\n participant: RemoteParticipant,\n ): void {\n const identity = participant.identity;\n log.info(`Track subscribed from \"${identity}\" (sid=${track.sid})`);\n\n // Track participant in memory\n this.memory?.addParticipant(identity);\n\n // Close existing AudioInput if this is a re-subscription\n const existing = this.audioInputs.get(identity);\n if (existing) {\n log.info(`Closing old AudioInput for \"${identity}\" (re-subscription)`);\n existing.close();\n }\n\n const audioInput = new AudioInput(track, identity);\n this.audioInputs.set(identity, audioInput);\n\n const sttStream = this.pipeline!.addParticipant(identity);\n\n this.pipeAudioToSTT(audioInput, sttStream, identity);\n }\n\n private handleTrackUnsubscribed(\n _track: RemoteAudioTrack,\n participant: RemoteParticipant,\n ): void {\n const identity = participant.identity;\n log.info(`Track unsubscribed from \"${identity}\"`);\n\n const input = this.audioInputs.get(identity);\n if (input) {\n input.close();\n this.audioInputs.delete(identity);\n }\n }\n\n private handleParticipantDisconnected(participant: RemoteParticipant): void {\n const identity = participant.identity;\n log.info(`Participant disconnected: \"${identity}\"`);\n\n const input = this.audioInputs.get(identity);\n if (input) {\n input.close();\n this.audioInputs.delete(identity);\n }\n\n this.pipeline?.removeParticipant(identity);\n\n // If no remote participants remain, stop the agent (process cleanup)\n if (this._running && this.connection?.room.remoteParticipants.size === 0) {\n log.info('All participants left — stopping agent');\n this.stop();\n }\n }\n\n private async pipeAudioToSTT(\n input: AudioInput,\n sttStream: { sendAudio(pcm16: Buffer): void },\n identity: string,\n ): Promise<void> {\n try {\n for await (const buffer of input.frames()) {\n if (!this._running) break;\n sttStream.sendAudio(buffer);\n }\n } catch (err) {\n if (this._running) {\n log.error(`Audio pipe error for \"${identity}\":`, err);\n }\n }\n }\n}\n","import { Room, LocalAudioTrack, AudioSource, TrackSource } from '@dtelecom/server-sdk-node';\nimport { createLogger } from '../utils/logger';\n\nconst log = createLogger('RoomConnection');\n\nexport interface RoomConnectionOptions {\n room: string;\n identity?: string;\n name?: string;\n\n // Mode 1: Generate token from credentials (existing)\n apiKey?: string;\n apiSecret?: string;\n\n // Mode 2: Use pre-signed token from x402 gateway\n token?: string;\n wsUrl?: string;\n}\n\nexport class RoomConnection {\n readonly room: Room;\n private audioSource: AudioSource | null = null;\n private localTrack: LocalAudioTrack | null = null;\n private _connected = false;\n\n constructor() {\n this.room = new Room();\n }\n\n get connected(): boolean {\n return this._connected;\n }\n\n /**\n * Connect to a dTelecom room.\n *\n * 1. Create an Ed25519 JWT via AccessToken\n * 2. Discover nearest SFU via getWsUrl()\n * 3. Connect Room via WebRTC\n * 4. Publish an audio track for the agent to speak through\n */\n async connect(options: RoomConnectionOptions): Promise<void> {\n const { room: roomName, identity = 'agent', name } = options;\n\n log.info(`Connecting to room \"${roomName}\" as \"${identity}\"...`);\n\n let jwt: string;\n let wsUrl: string;\n\n if (options.token && options.wsUrl) {\n // Mode 2: Use pre-signed token from x402 gateway\n jwt = options.token;\n wsUrl = options.wsUrl;\n } else if (options.apiKey && options.apiSecret) {\n // Mode 1: Generate token from credentials\n const { AccessToken } = await import('@dtelecom/server-sdk-js');\n\n const token = new AccessToken(options.apiKey, options.apiSecret, {\n identity,\n name: name ?? identity,\n });\n token.addGrant({\n roomJoin: true,\n room: roomName,\n canPublish: true,\n canSubscribe: true,\n canPublishData: true,\n });\n\n wsUrl = await token.getWsUrl();\n jwt = token.toJwt();\n } else {\n throw new Error('Either (apiKey + apiSecret) or (token + wsUrl) must be provided');\n }\n\n log.info(`SFU URL: ${wsUrl}`);\n\n // Connect\n await this.room.connect(wsUrl, jwt, { autoSubscribe: true });\n this._connected = true;\n\n log.info('Connected successfully');\n }\n\n /**\n * Publish an audio track so the agent can speak.\n * Returns the AudioSource to feed PCM16 audio into.\n */\n async publishAudioTrack(): Promise<AudioSource> {\n if (this.audioSource) return this.audioSource;\n\n // 48kHz mono — matches Opus/WebRTC native rate, no resampling needed\n this.audioSource = new AudioSource(48000, 1);\n this.localTrack = LocalAudioTrack.createAudioTrack('agent-voice', this.audioSource);\n\n await this.room.localParticipant.publishTrack(this.localTrack, {\n name: 'agent-voice',\n source: TrackSource.MICROPHONE,\n });\n\n log.info('Audio track published');\n return this.audioSource;\n }\n\n /** Disconnect from the room and clean up resources. */\n async disconnect(): Promise<void> {\n if (!this._connected) return;\n\n if (this.localTrack) {\n await this.room.localParticipant.unpublishTrack(this.localTrack);\n this.localTrack = null;\n }\n\n if (this.audioSource) {\n this.audioSource.destroy();\n this.audioSource = null;\n }\n\n await this.room.disconnect();\n this._connected = false;\n\n log.info('Disconnected from room');\n }\n}\n","import { RemoteAudioTrack, AudioStream, AudioFrame } from '@dtelecom/server-sdk-node';\nimport { createLogger } from '../utils/logger';\n\nconst log = createLogger('AudioInput');\n\nexport class AudioInput {\n readonly participantIdentity: string;\n private stream: AudioStream;\n private _closed = false;\n private frameCount = 0;\n\n constructor(track: RemoteAudioTrack, participantIdentity: string) {\n this.participantIdentity = participantIdentity;\n // 16kHz mono — standard for STT\n this.stream = track.createStream(16000, 1);\n log.info(`AudioInput created for \"${participantIdentity}\" (trackSid=${track.sid})`);\n }\n\n get closed(): boolean {\n return this._closed;\n }\n\n /**\n * Async iterate over PCM16 buffers from this participant.\n * Each yielded Buffer is 16kHz mono PCM16 LE.\n */\n async *frames(): AsyncGenerator<Buffer> {\n for await (const frame of this.stream) {\n if (this._closed) break;\n this.frameCount++;\n if (this.frameCount === 1 || this.frameCount % 500 === 0) {\n log.info(`[${this.participantIdentity}] frame #${this.frameCount}`);\n }\n yield frame.toBuffer();\n }\n log.info(`[${this.participantIdentity}] frame iterator ended (total: ${this.frameCount})`);\n }\n\n /** Async iterate over AudioFrame objects. */\n async *audioFrames(): AsyncGenerator<AudioFrame> {\n for await (const frame of this.stream) {\n if (this._closed) break;\n yield frame;\n }\n }\n\n close(): void {\n if (this._closed) return;\n this._closed = true;\n this.stream.close();\n log.debug(`AudioInput closed for participant \"${this.participantIdentity}\"`);\n }\n}\n","import { AudioSource, AudioFrame } from '@dtelecom/server-sdk-node';\nimport { writeFileSync, appendFileSync, existsSync, mkdirSync } from 'fs';\nimport { join } from 'path';\nimport { createLogger } from '../utils/logger';\n\nconst log = createLogger('AudioOutput');\n\n/** Rate at which we write audio (48kHz mono, 20ms frames = 960 samples) */\nconst SAMPLE_RATE = 48000;\nconst CHANNELS = 1;\nconst FRAME_DURATION_MS = 20;\nconst SAMPLES_PER_FRAME = (SAMPLE_RATE * FRAME_DURATION_MS) / 1000; // 960 at 48kHz\n\n/** Pre-allocated silence frame */\nconst SILENCE = new Int16Array(SAMPLES_PER_FRAME);\n\nexport class AudioOutput {\n private source: AudioSource;\n private _playing = false;\n private _responding = false;\n private _stopped = false;\n private silenceInterval: ReturnType<typeof setInterval> | null = null;\n\n /** Resolves when the RTP transport is ready and initial silence has been sent. */\n readonly whenReady: Promise<void>;\n private _resolveReady?: () => void;\n\n /** When set, raw PCM from TTS is saved to this directory as WAV files for debugging. */\n dumpDir: string | null = null;\n private dumpCounter = 0;\n\n constructor(source: AudioSource) {\n this.source = source;\n if (source.ready) {\n this.whenReady = Promise.resolve();\n } else {\n this.whenReady = new Promise((resolve) => { this._resolveReady = resolve; });\n }\n }\n\n get playing(): boolean {\n return this._playing;\n }\n\n /**\n * Mark the start of a multi-sentence response.\n * Suppresses silence injection between sentences so partial frames\n * in AudioSource's buffer don't get corrupted by interleaved silence.\n */\n beginResponse(): void {\n this._responding = true;\n }\n\n /** Mark the end of a response — re-enable silence keepalive. */\n endResponse(): void {\n this._responding = false;\n }\n\n /**\n * Start sparse silence keepalive to prevent the SFU from dropping the track.\n * With Opus DTX enabled, the encoder handles silence natively — we only need\n * an occasional packet to keep the SSRC alive.\n *\n * Waits for the RTP transport to be ready before sending — no frames are\n * wasted before DTLS is connected.\n */\n startSilence(): void {\n if (this.silenceInterval) return;\n\n const startKeepalive = () => {\n log.debug('Transport ready — sending initial silence burst + starting 3s keepalive');\n\n // Send 300ms of silence so the SFU starts forwarding the track and\n // the client's jitter buffer is primed before real speech arrives.\n for (let i = 0; i < 15; i++) {\n this.sendSilenceFrame();\n }\n this._resolveReady?.();\n\n this.silenceInterval = setInterval(() => {\n if (!this._playing && !this._responding && !this._stopped) {\n this.sendSilenceFrame();\n }\n }, 3000);\n };\n\n if (this.source.ready) {\n startKeepalive();\n } else {\n log.debug('Waiting for transport before starting silence keepalive...');\n this.source.onReady = () => startKeepalive();\n }\n }\n\n private sendSilenceFrame(): void {\n const frame = new AudioFrame(SILENCE, SAMPLE_RATE, CHANNELS, SAMPLES_PER_FRAME);\n this.source.captureFrame(frame).catch((err) => {\n log.warn('Failed to send silence frame:', err);\n });\n }\n\n /**\n * Write a PCM16 buffer to the audio output.\n * The buffer is split into 20ms frames and fed to AudioSource.\n */\n async writeBuffer(pcm16: Buffer): Promise<void> {\n this._playing = true;\n try {\n await this.writeFrames(pcm16);\n } finally {\n this._playing = false;\n }\n }\n\n /**\n * Write a stream of PCM16 buffers (from TTS) to the audio output.\n * Supports cancellation via AbortSignal.\n */\n async writeStream(\n stream: AsyncIterable<Buffer>,\n signal?: AbortSignal,\n ): Promise<void> {\n this._playing = true;\n const streamStart = performance.now();\n let chunkCount = 0;\n let totalBytes = 0;\n\n log.debug('writeStream: started');\n\n // Collect raw TTS chunks for WAV dump if enabled\n const rawChunks: Buffer[] | null = this.dumpDir ? [] : null;\n\n try {\n for await (const chunk of stream) {\n if (signal?.aborted) {\n log.debug(`writeStream: cancelled after ${chunkCount} chunks, ${(performance.now() - streamStart).toFixed(0)}ms`);\n break;\n }\n chunkCount++;\n totalBytes += chunk.byteLength;\n rawChunks?.push(Buffer.from(chunk));\n await this.writeFrames(chunk);\n }\n } finally {\n this._playing = false;\n const elapsed = performance.now() - streamStart;\n const audioDurationMs = (totalBytes / 2) / SAMPLE_RATE * 1000;\n log.info(\n `writeStream: done — ${chunkCount} chunks, ${totalBytes} bytes, ` +\n `audio=${audioDurationMs.toFixed(0)}ms, wall=${elapsed.toFixed(0)}ms`,\n );\n\n // Save raw TTS audio as WAV for debugging\n if (rawChunks && rawChunks.length > 0 && this.dumpDir) {\n try {\n if (!existsSync(this.dumpDir)) mkdirSync(this.dumpDir, { recursive: true });\n const filePath = join(this.dumpDir, `tts-raw-${++this.dumpCounter}.wav`);\n writeWav(filePath, rawChunks, SAMPLE_RATE);\n log.info(`writeStream: saved raw TTS to ${filePath}`);\n } catch (err) {\n log.warn('writeStream: failed to save WAV dump:', err);\n }\n }\n }\n }\n\n /**\n * Split a PCM16 buffer into 20ms frames and write them at real-time pace.\n * Partial frames at the end are sent directly — AudioSource handles\n * accumulation in its internal buffer.\n */\n private async writeFrames(pcm16: Buffer): Promise<void> {\n // Ensure aligned buffer for Int16Array.\n // ws library may deliver Buffers with odd byteOffset.\n const aligned = Buffer.alloc(pcm16.byteLength);\n pcm16.copy(aligned);\n const samples = new Int16Array(\n aligned.buffer,\n aligned.byteOffset,\n aligned.byteLength / 2,\n );\n\n let offset = 0;\n while (offset < samples.length) {\n const end = Math.min(offset + SAMPLES_PER_FRAME, samples.length);\n const frameSamples = samples.subarray(offset, end);\n\n const frame = new AudioFrame(\n frameSamples,\n SAMPLE_RATE,\n CHANNELS,\n frameSamples.length,\n );\n\n await this.source.captureFrame(frame);\n\n // Only pace full frames — partial frames don't produce an Opus packet,\n // they just accumulate in AudioSource's buffer. Sleeping for them\n // causes audio to play slower than real-time.\n if (frameSamples.length === SAMPLES_PER_FRAME) {\n await sleep(FRAME_DURATION_MS);\n }\n\n offset = end;\n }\n }\n\n /**\n * Write silence frames for the given duration.\n * Used to pad the end of a response so the last Opus frame is fully flushed\n * and the audio doesn't cut off abruptly.\n */\n async writeSilence(durationMs: number): Promise<void> {\n const frameCount = Math.ceil(durationMs / FRAME_DURATION_MS);\n for (let i = 0; i < frameCount; i++) {\n const frame = new AudioFrame(SILENCE, SAMPLE_RATE, CHANNELS, SAMPLES_PER_FRAME);\n await this.source.captureFrame(frame);\n await sleep(FRAME_DURATION_MS);\n }\n }\n\n /** Flush any buffered audio in AudioSource */\n flush(): void {\n this.source.flush();\n this._playing = false;\n }\n\n /** Stop the silence keepalive */\n stop(): void {\n this._stopped = true;\n if (this.silenceInterval) {\n clearInterval(this.silenceInterval);\n this.silenceInterval = null;\n }\n }\n}\n\nfunction sleep(ms: number): Promise<void> {\n return new Promise((resolve) => setTimeout(resolve, ms));\n}\n\n/** Write a WAV file header + PCM data for debugging. */\nfunction writeWav(filePath: string, pcmChunks: Buffer[], sampleRate: number): void {\n const dataSize = pcmChunks.reduce((sum, b) => sum + b.byteLength, 0);\n const header = Buffer.alloc(44);\n header.write('RIFF', 0);\n header.writeUInt32LE(36 + dataSize, 4);\n header.write('WAVE', 8);\n header.write('fmt ', 12);\n header.writeUInt32LE(16, 16); // fmt chunk size\n header.writeUInt16LE(1, 20); // PCM format\n header.writeUInt16LE(1, 22); // mono\n header.writeUInt32LE(sampleRate, 24);\n header.writeUInt32LE(sampleRate * 2, 28); // byte rate\n header.writeUInt16LE(2, 32); // block align\n header.writeUInt16LE(16, 34); // bits per sample\n header.write('data', 36);\n header.writeUInt32LE(dataSize, 40);\n\n writeFileSync(filePath, header);\n for (const chunk of pcmChunks) {\n appendFileSync(filePath, chunk);\n }\n}\n","/**\n * Pipeline — coordinates the STT -> LLM -> TTS flow.\n *\n * Uses a producer/consumer pattern:\n * - Producer: LLM tokens -> sentence splitter -> sentence queue\n * - Consumer: sentence queue -> TTS -> audio output\n * Both run concurrently so audio playback never blocks LLM consumption.\n *\n * Supports barge-in (interruption cancels both producer and consumer).\n */\n\nimport { EventEmitter } from 'events';\nimport type {\n STTPlugin,\n STTStream,\n LLMPlugin,\n TTSPlugin,\n TranscriptionResult,\n RespondMode,\n PipelineOptions,\n PipelineEvents,\n AgentState,\n ToolDefinition,\n} from './types';\nimport { ContextManager } from './context-manager';\nimport { SentenceSplitter } from './sentence-splitter';\nimport { TurnDetector } from './turn-detector';\nimport { BargeIn } from './barge-in';\nimport type { AudioOutput } from '../room/audio-output';\nimport type { RoomMemory } from '../memory/room-memory';\nimport { createLogger } from '../utils/logger';\n\nconst log = createLogger('Pipeline');\n\n/**\n * Estimated latency from AudioSource.captureFrame() to the client hearing it:\n * Opus encode → RTP → SFU → client → jitter buffer → decode.\n * We delay the \"speaking: false\" emission by this amount so the UI status\n * matches what the user actually hears.\n */\nconst AUDIO_DRAIN_MS = 800;\n\nfunction sleep(ms: number): Promise<void> {\n return new Promise((resolve) => setTimeout(resolve, ms));\n}\n\n/**\n * Start TTS synthesis in the background, buffering chunks as they arrive.\n * Returns a factory that produces an async generator yielding the buffered chunks.\n * This allows the next sentence's TTS to run while the current sentence plays.\n */\nfunction prefetchTTS(\n tts: TTSPlugin,\n text: string,\n signal?: AbortSignal,\n): () => AsyncGenerator<Buffer> {\n const buffer: Buffer[] = [];\n let done = false;\n let error: unknown = null;\n let wake: (() => void) | null = null;\n\n const notify = () => { if (wake) { const w = wake; wake = null; w(); } };\n\n // Fire-and-forget: consume TTS into buffer immediately\n void (async () => {\n try {\n const stream = tts.synthesize(text, signal);\n for await (const chunk of stream) {\n if (signal?.aborted) break;\n buffer.push(chunk);\n notify();\n }\n } catch (e: unknown) {\n if (!(e instanceof Error && e.name === 'AbortError')) error = e;\n } finally {\n done = true;\n notify();\n }\n })();\n\n // Return factory for async generator that drains the buffer\n return async function* () {\n let index = 0;\n while (true) {\n if (signal?.aborted) return;\n if (error) throw error;\n if (index < buffer.length) {\n yield buffer[index++];\n continue;\n }\n if (done) return;\n await new Promise<void>((r) => { wake = r; });\n }\n };\n}\n\nexport class Pipeline extends EventEmitter {\n private readonly stt: STTPlugin;\n private readonly llm: LLMPlugin;\n private readonly tts: TTSPlugin | undefined;\n private readonly audioOutput: AudioOutput;\n private readonly context: ContextManager;\n private readonly turnDetector: TurnDetector;\n private readonly bargeIn: BargeIn;\n private readonly splitter: SentenceSplitter;\n private readonly respondMode: RespondMode;\n private readonly agentName: string;\n private readonly nameVariants: string[];\n private readonly beforeRespond?: (speaker: string, text: string) => boolean | Promise<boolean>;\n private readonly memory?: RoomMemory;\n private readonly tools?: ToolDefinition[];\n\n /** Strip provider-specific markup (e.g. SSML lang tags) for display. */\n private cleanText(text: string): string {\n return this.tts?.cleanText ? this.tts.cleanText(text) : text;\n }\n\n /** Active STT streams, keyed by participant identity */\n private sttStreams = new Map<string, STTStream>();\n\n private _processing = false;\n private _running = false;\n private _agentState: AgentState = 'idle';\n /** Queued turn while current one is still processing */\n private pendingTurn: { speaker: string; text: string } | null = null;\n\n constructor(options: PipelineOptions) {\n super();\n this.stt = options.stt;\n this.llm = options.llm;\n this.tts = options.tts;\n this.audioOutput = options.audioOutput;\n this.respondMode = options.respondMode ?? 'always';\n this.agentName = (options.agentName ?? 'assistant').toLowerCase();\n this.nameVariants = (options.nameVariants ?? []).map((n) => n.toLowerCase());\n this.beforeRespond = options.beforeRespond;\n this.memory = options.memory;\n this.tools = options.tools;\n this.context = new ContextManager({\n instructions: options.instructions,\n maxContextTokens: options.maxContextTokens,\n });\n this.turnDetector = new TurnDetector({\n silenceTimeoutMs: options.silenceTimeoutMs,\n });\n this.bargeIn = new BargeIn();\n this.splitter = new SentenceSplitter();\n\n this.turnDetector.onTurnEnd = () => {};\n\n this.bargeIn.onInterrupt = () => {\n this.audioOutput.flush();\n this.splitter.reset();\n this.setAgentState('idle');\n };\n\n // Warm up LLM and TTS separately so say() can start as soon as TTS is ready\n this._ttsWarmupPromise = this.tts?.warmup\n ? this.tts.warmup().catch((err: unknown) => { log.warn('TTS warmup failed (non-fatal):', err); })\n : Promise.resolve();\n this._llmWarmupPromise = this.llm.warmup\n ? this.llm.warmup(options.instructions).catch((err: unknown) => { log.warn('LLM warmup failed (non-fatal):', err); })\n : Promise.resolve();\n this._audioReadyPromise = this.audioOutput.whenReady;\n this._warmupPromise = Promise.all([this._ttsWarmupPromise, this._llmWarmupPromise, this._audioReadyPromise]).then(() => {});\n }\n\n private readonly _warmupPromise: Promise<void>;\n private readonly _ttsWarmupPromise: Promise<void>;\n private readonly _llmWarmupPromise: Promise<void>;\n private readonly _audioReadyPromise: Promise<void>;\n\n get processing(): boolean {\n return this._processing;\n }\n\n get running(): boolean {\n return this._running;\n }\n\n get agentState(): AgentState {\n return this._agentState;\n }\n\n private setAgentState(state: AgentState): void {\n if (this._agentState !== state) {\n this._agentState = state;\n this.emit('agentState', state);\n }\n }\n\n addParticipant(identity: string): STTStream {\n const existing = this.sttStreams.get(identity);\n if (existing) {\n existing.close();\n this.sttStreams.delete(identity);\n log.info(`Replacing STT stream for \"${identity}\"`);\n }\n\n const stream = this.stt.createStream();\n this.sttStreams.set(identity, stream);\n this._running = true;\n\n stream.on('transcription', (result) => {\n this.handleTranscription(identity, result);\n });\n\n stream.on('error', (error) => {\n log.error(`STT error for ${identity}:`, error);\n this.emit('error', error);\n });\n\n log.info(`STT stream started for participant \"${identity}\"`);\n return stream;\n }\n\n async removeParticipant(identity: string): Promise<void> {\n const stream = this.sttStreams.get(identity);\n if (stream) {\n await stream.close();\n this.sttStreams.delete(identity);\n log.info(`STT stream removed for participant \"${identity}\"`);\n }\n }\n\n async stop(): Promise<void> {\n this._running = false;\n this.turnDetector.reset();\n this.bargeIn.reset();\n this.splitter.reset();\n\n for (const [, stream] of this.sttStreams) {\n await stream.close();\n }\n this.sttStreams.clear();\n\n this.tts?.close?.();\n\n log.info('Pipeline stopped');\n }\n\n getContextManager(): ContextManager {\n return this.context;\n }\n\n /** Switch STT language on all active streams (e.g. for bilingual lessons). */\n setSTTLanguage(language: string, options?: { forceWhisper?: boolean }): void {\n for (const [identity, stream] of this.sttStreams) {\n if (stream.setLanguage) {\n stream.setLanguage(language, options);\n log.info(`STT language → ${language}${options?.forceWhisper ? ' (whisper)' : ''} for \"${identity}\"`);\n }\n }\n }\n\n private lastFinalAt = 0;\n private lastSttDuration = 0;\n\n private async handleTranscription(speaker: string, result: TranscriptionResult): Promise<void> {\n this.emit('transcription', { ...result, speaker });\n\n // Any interim (including empty VAD speech_start) → user is speaking\n if (!result.isFinal) {\n this.setAgentState('listening');\n }\n\n if (this.audioOutput.playing && result.text.trim().length > 0) {\n this.bargeIn.trigger();\n }\n\n if (result.isFinal && result.text.trim()) {\n const text = result.text.trim();\n this.lastFinalAt = performance.now();\n this.lastSttDuration = result.sttDuration ?? 0;\n\n // Store every turn to memory (async, non-blocking)\n this.memory?.storeTurn(speaker, text, false);\n\n if (await this.shouldRespond(speaker, text)) {\n this.processTurn(speaker, text);\n } else {\n log.info(`Not responding to \"${speaker}\": \"${text.slice(0, 60)}\" (mode=${this.respondMode})`);\n this.setAgentState('idle');\n }\n } else if (result.isFinal) {\n // Empty final or no text — user stopped speaking without a usable turn\n this.setAgentState('idle');\n }\n }\n\n /**\n * Determine if the agent should respond to this turn.\n * In 'always' mode: responds to everything.\n * In 'addressed' mode: only when agent name is mentioned + optional beforeRespond hook.\n */\n private async shouldRespond(speaker: string, text: string): Promise<boolean> {\n if (this.respondMode === 'always') return true;\n\n // Check if agent name or variants are mentioned\n const lower = text.toLowerCase();\n const nameMatch = lower.includes(this.agentName) ||\n this.nameVariants.some((v) => lower.includes(v));\n\n if (!nameMatch) return false;\n\n // If beforeRespond hook exists, let it decide\n if (this.beforeRespond) {\n return this.beforeRespond(speaker, text);\n }\n\n return true;\n }\n\n private async processTurn(speaker: string, text: string): Promise<void> {\n if (this._processing) {\n log.info(`Queuing turn (current still processing): \"${text}\"`);\n this.pendingTurn = { speaker, text };\n this.bargeIn.trigger();\n return;\n }\n\n this._processing = true;\n await this._warmupPromise;\n\n // ── Latency tracking ──\n const tSpeechEnd = this.lastFinalAt;\n const sttDuration = this.lastSttDuration;\n let tLlmFirstToken = 0;\n let tFirstSentence = 0;\n let tFirstAudioPlayed = 0;\n\n log.info(`Processing turn from \"${speaker}\": ${text}`);\n\n try {\n this.context.addUserTurn(speaker, text);\n\n if (this.context.shouldSummarize()) {\n await this.context.summarize(this.llm);\n }\n\n const signal = this.bargeIn.startCycle();\n\n // Search memory for relevant past context\n let memoryContext = '';\n if (this.memory) {\n try {\n memoryContext = await this.memory.searchRelevant(text);\n } catch (err) {\n log.warn('Memory search failed:', err);\n }\n }\n\n const messages = this.context.buildMessages(memoryContext || undefined);\n let fullResponse = '';\n\n this.setAgentState('thinking');\n\n // ── Producer/Consumer pattern ──\n const sentenceQueue: string[] = [];\n let producerDone = false;\n let wakeConsumer: (() => void) | null = null;\n\n const wake = () => { wakeConsumer?.(); };\n\n /** Push a sentence to the queue with first-sentence tracking. */\n let isFirstSentence = true;\n const pushSentence = (text: string) => {\n if (signal.aborted) return;\n if (isFirstSentence) {\n tFirstSentence = performance.now();\n isFirstSentence = false;\n log.info(`first_sentence: ${(tFirstSentence - tSpeechEnd).toFixed(0)}ms — \"${text.slice(0, 60)}\"`);\n }\n sentenceQueue.push(text);\n wake();\n };\n\n // ── Producer: consume LLM stream, split into sentences ──\n const MAX_LLM_RETRIES = 2;\n\n let toolCallEmitted = false;\n\n const producer = async () => {\n const defaultLang = this.tts?.defaultLanguage;\n\n for (let attempt = 0; attempt <= MAX_LLM_RETRIES; attempt++) {\n if (signal.aborted) break;\n\n if (attempt > 0) {\n log.warn(`LLM retry ${attempt}/${MAX_LLM_RETRIES}...`);\n this.splitter.reset();\n }\n\n let isFirstChunk = true;\n const segBuf: Array<{ lang: string; text: string }> = [];\n\n const flushSegments = () => {\n if (segBuf.length === 0) return;\n\n const combined = segBuf\n .map((s) =>\n s.lang !== defaultLang\n ? `<lang xml:lang=\"${s.lang}\">${s.text}</lang>`\n : s.text,\n )\n .join(' ');\n segBuf.length = 0;\n pushSentence(combined);\n };\n\n const llmStream = this.llm.chat(messages, signal, { tools: this.tools });\n try {\n while (!signal.aborted) {\n const { value: chunk, done } = await llmStream.next();\n if (done || !chunk) break;\n if (signal.aborted) break;\n\n if (chunk.type === 'segment' && chunk.segment) {\n // Structured output: accumulate segments, flush at sentence boundaries\n if (isFirstChunk) {\n tLlmFirstToken = performance.now();\n isFirstChunk = false;\n log.info(`llm_first_segment: ${(tLlmFirstToken - tSpeechEnd).toFixed(0)}ms`);\n }\n\n // Track clean text for context/memory (not JSON)\n if (fullResponse) fullResponse += ' ';\n fullResponse += chunk.segment.text;\n\n segBuf.push(chunk.segment);\n\n // Flush at sentence boundaries (.!? optionally followed by quotes/parens)\n if (/[.!?][\"'»)]*\\s*$/.test(chunk.segment.text)) {\n flushSegments();\n }\n } else if (chunk.type === 'token' && chunk.token) {\n // Plain text mode (no structured output)\n if (isFirstChunk) {\n tLlmFirstToken = performance.now();\n isFirstChunk = false;\n log.info(`llm_first_token: ${(tLlmFirstToken - tSpeechEnd).toFixed(0)}ms`);\n }\n\n fullResponse += chunk.token;\n\n const sentences = this.splitter.push(chunk.token);\n for (const sentence of sentences) {\n pushSentence(sentence);\n }\n } else if (chunk.type === 'tool_call' && chunk.toolCall) {\n log.info(`Tool call: ${chunk.toolCall.name}(${chunk.toolCall.arguments})`);\n toolCallEmitted = true;\n this.emit('toolCall', chunk.toolCall);\n }\n }\n } finally {\n await llmStream.return(undefined);\n }\n\n // Flush remaining text\n if (!signal.aborted) {\n flushSegments();\n const remaining = this.splitter.flush();\n if (remaining) {\n pushSentence(remaining);\n }\n\n if (fullResponse.trim() || toolCallEmitted) {\n break; // Got output or tool call — done\n }\n\n log.warn(`LLM produced no output (attempt ${attempt + 1}/${MAX_LLM_RETRIES + 1})`);\n }\n }\n\n producerDone = true;\n wake();\n };\n\n // ── Consumer: synthesize sentences and play audio ──\n // beginResponse/endResponse suppresses silence injection between\n // sentences so partial frames in AudioSource don't get corrupted.\n // Pre-fetches TTS for the next sentence while current one plays.\n const consumer = async () => {\n this.audioOutput.beginResponse();\n type Prefetched = { sentence: string; streamFn: () => AsyncGenerator<Buffer> };\n const state: { prefetched: Prefetched | null } = { prefetched: null };\n try {\n while (true) {\n if (signal.aborted) break;\n\n let sentence: string;\n let existingStream: AsyncGenerator<Buffer> | undefined;\n\n if (state.prefetched) {\n sentence = state.prefetched.sentence;\n existingStream = state.prefetched.streamFn();\n state.prefetched = null;\n } else if (sentenceQueue.length > 0) {\n sentence = sentenceQueue.shift()!;\n if (!/\\w/.test(sentence)) {\n log.debug(`Skipping non-word sentence: \"${sentence}\"`);\n continue;\n }\n existingStream = undefined;\n } else if (producerDone) {\n break;\n } else {\n await new Promise<void>((resolve) => {\n wakeConsumer = resolve;\n });\n wakeConsumer = null;\n continue;\n }\n\n // Pre-fetch next sentence TTS while current one plays\n const tryPrefetch = () => {\n if (state.prefetched || !this.tts) return;\n if (sentenceQueue.length > 0) {\n const next = sentenceQueue.shift()!;\n if (/\\w/.test(next)) {\n state.prefetched = { sentence: next, streamFn: prefetchTTS(this.tts, next, signal) };\n }\n }\n };\n tryPrefetch();\n\n try {\n await this.synthesizeAndPlay(sentence, signal, (t) => {\n if (!tFirstAudioPlayed) {\n tFirstAudioPlayed = t;\n this.setAgentState('speaking');\n }\n this.emit('sentence', this.cleanText(sentence), sentence);\n tryPrefetch(); // also try when first audio arrives (more sentences may be ready)\n }, existingStream);\n } catch (ttsErr: unknown) {\n // TTS error on a prefetched sentence should not kill the turn —\n // previous sentences already played successfully.\n if (ttsErr instanceof Error && ttsErr.name === 'AbortError') throw ttsErr;\n log.warn(`TTS error for sentence (skipping): \"${sentence.slice(0, 40)}\"`, ttsErr);\n }\n }\n } finally {\n if (!signal.aborted) {\n await this.audioOutput.writeSilence(40);\n }\n this.audioOutput.endResponse();\n }\n };\n\n await Promise.all([producer(), consumer()]);\n\n // ── Latency summary ──\n // STT: last interim (≈ end of speech) → final transcript received\n // LLM: final transcript → first complete sentence (TTFT + accumulation)\n // TTS: first sentence ready → first audio chunk to WebRTC\n // Overall: STT + LLM + TTS\n const ttftMs = tLlmFirstToken ? tLlmFirstToken - tSpeechEnd : 0;\n const llmMs = tFirstSentence ? tFirstSentence - tSpeechEnd : 0;\n const ttsMs = tFirstAudioPlayed && tFirstSentence ? tFirstAudioPlayed - tFirstSentence : 0;\n const overallMs = sttDuration + llmMs + ttsMs;\n\n log.info(\n `LATENCY \"${text.slice(0, 30)}\": ` +\n `STT=${sttDuration.toFixed(0)}ms ` +\n `LLM=${llmMs.toFixed(0)}ms (TTFT=${ttftMs.toFixed(0)}ms) ` +\n `TTS=${ttsMs.toFixed(0)}ms ` +\n `Overall=${overallMs.toFixed(0)}ms`,\n );\n\n if (fullResponse.trim()) {\n this.context.addAgentTurn(fullResponse.trim());\n this.memory?.storeTurn('assistant', fullResponse.trim(), true);\n this.emit('response', this.cleanText(fullResponse.trim()));\n }\n\n // Wait for audio pipeline to drain before signaling \"listening\"\n // (AudioSource → Opus → RTP → SFU → client decode)\n await sleep(AUDIO_DRAIN_MS);\n this.setAgentState('idle');\n } catch (err: unknown) {\n if (err instanceof Error && err.name === 'AbortError') {\n log.debug('Turn processing aborted (barge-in)');\n } else {\n log.error('Error processing turn:', err);\n this.emit('error', err instanceof Error ? err : new Error(String(err)));\n }\n } finally {\n this._processing = false;\n this.bargeIn.reset();\n\n if (this.pendingTurn) {\n const { speaker: nextSpeaker, text: nextText } = this.pendingTurn;\n this.pendingTurn = null;\n log.info(`Processing queued turn from \"${nextSpeaker}\": ${nextText}`);\n this.processTurn(nextSpeaker, nextText);\n }\n }\n }\n\n /**\n * Speak text directly via TTS, bypassing the LLM.\n * Supports barge-in — if a participant speaks, the playback is cut short.\n * Adds the text to conversation context so the LLM knows what was said.\n */\n async say(text: string): Promise<void> {\n if (this._processing) {\n log.warn('say() called while processing — skipping');\n return;\n }\n\n this._processing = true;\n await Promise.all([this._ttsWarmupPromise, this._audioReadyPromise]);\n log.info(`say(): \"${text.slice(0, 60)}\"`);\n\n try {\n const signal = this.bargeIn.startCycle();\n this.audioOutput.beginResponse();\n this.setAgentState('thinking');\n\n await this.synthesizeAndPlay(text, signal, () => {\n this.setAgentState('speaking');\n this.emit('sentence', this.cleanText(text), text);\n });\n\n if (!signal.aborted) {\n await this.audioOutput.writeSilence(40);\n this.context.addAgentTurn(text);\n this.memory?.storeTurn('assistant', text, true);\n this.emit('response', this.cleanText(text));\n }\n\n // Wait for audio pipeline to drain before signaling \"listening\"\n await sleep(AUDIO_DRAIN_MS);\n this.setAgentState('idle');\n } catch (err: unknown) {\n if (err instanceof Error && err.name === 'AbortError') {\n log.debug('say() aborted (barge-in)');\n } else {\n log.error('Error in say():', err);\n this.emit('error', err instanceof Error ? err : new Error(String(err)));\n }\n } finally {\n this._processing = false;\n this.audioOutput.endResponse();\n this.bargeIn.reset();\n\n if (this.pendingTurn) {\n const { speaker: nextSpeaker, text: nextText } = this.pendingTurn;\n this.pendingTurn = null;\n log.info(`Processing queued turn from \"${nextSpeaker}\": ${nextText}`);\n this.processTurn(nextSpeaker, nextText);\n }\n }\n }\n\n private async synthesizeAndPlay(\n text: string,\n signal: AbortSignal,\n onFirstAudio: (timestamp: number) => void,\n existingStream?: AsyncGenerator<Buffer>,\n ): Promise<void> {\n if (!this.tts || signal.aborted) {\n log.info(`[Agent says]: ${text}`);\n return;\n }\n\n try {\n const ttsStart = performance.now();\n let firstChunk = true;\n let ttsChunkCount = 0;\n\n const ttsStream = existingStream ?? this.tts.synthesize(text, signal);\n const measuredStream = async function* () {\n for await (const chunk of ttsStream) {\n ttsChunkCount++;\n if (firstChunk) {\n firstChunk = false;\n const now = performance.now();\n log.info(`tts_first_audio: ${(now - ttsStart).toFixed(0)}ms for \"${text.slice(0, 40)}\"`);\n onFirstAudio(now);\n }\n yield chunk;\n }\n };\n\n await this.audioOutput.writeStream(measuredStream(), signal);\n log.info(`synthesizeAndPlay done: ${(performance.now() - ttsStart).toFixed(0)}ms, ${ttsChunkCount} chunks for \"${text.slice(0, 40)}\"`);\n } catch (err: unknown) {\n if (err instanceof Error && err.name === 'AbortError') return;\n throw err;\n }\n }\n}\n","import type { Message, LLMPlugin } from './types';\nimport { createLogger } from '../utils/logger';\n\nconst log = createLogger('ContextManager');\n\n/** Rough token estimate: 1 token ~ 4 chars */\nfunction estimateTokens(text: string): number {\n return Math.ceil(text.length / 4);\n}\n\nexport interface ContextManagerOptions {\n /** System instructions for the agent */\n instructions: string;\n /** Max tokens before triggering summarization (default: 5000) */\n maxContextTokens?: number;\n /** Number of recent turns to keep verbatim (default: 4) */\n recentTurnsToKeep?: number;\n}\n\ninterface Turn {\n speaker: string;\n text: string;\n isAgent: boolean;\n timestamp: number;\n}\n\nexport class ContextManager {\n private readonly instructions: string;\n private readonly maxContextTokens: number;\n private readonly recentTurnsToKeep: number;\n\n private turns: Turn[] = [];\n private summary: string | null = null;\n\n constructor(options: ContextManagerOptions) {\n this.instructions = options.instructions;\n this.maxContextTokens = options.maxContextTokens ?? 5000;\n this.recentTurnsToKeep = options.recentTurnsToKeep ?? 4;\n }\n\n /** Add a user's speech turn to the conversation */\n addUserTurn(speaker: string, text: string): void {\n this.turns.push({\n speaker,\n text,\n isAgent: false,\n timestamp: Date.now(),\n });\n }\n\n /** Add the agent's response to the conversation */\n addAgentTurn(text: string): void {\n this.turns.push({\n speaker: 'assistant',\n text,\n isAgent: true,\n timestamp: Date.now(),\n });\n }\n\n /**\n * Build the messages array for the LLM call.\n *\n * Structure:\n * [system prompt]\n * [memory context, if provided]\n * [conversation summary, if any]\n * [recent verbatim turns]\n *\n * @param memoryContext - Optional relevant context injected by the application\n */\n buildMessages(memoryContext?: string): Message[] {\n const messages: Message[] = [];\n\n // System prompt\n messages.push({ role: 'system', content: this.instructions });\n\n // Application-injected memory context (if available)\n if (memoryContext) {\n messages.push({\n role: 'system',\n content: `Relevant context from past conversations:\\n${memoryContext}`,\n });\n }\n\n // Conversation summary (if summarization has occurred)\n if (this.summary) {\n messages.push({\n role: 'system',\n content: `Conversation summary so far:\\n${this.summary}`,\n });\n }\n\n // Format turns as messages\n const turnsToInclude = this.summary\n ? this.turns.slice(-this.recentTurnsToKeep)\n : this.turns;\n\n for (const turn of turnsToInclude) {\n if (turn.isAgent) {\n messages.push({ role: 'assistant', content: turn.text });\n } else {\n messages.push({\n role: 'user',\n content: `[${turn.speaker}]: ${turn.text}`,\n });\n }\n }\n\n return messages;\n }\n\n /** Check if summarization should be triggered */\n shouldSummarize(): boolean {\n const totalTokens = this.turns.reduce(\n (acc, t) => acc + estimateTokens(t.text) + 10,\n estimateTokens(this.instructions),\n );\n return totalTokens > this.maxContextTokens;\n }\n\n /**\n * Summarize older turns using the LLM.\n * Keeps the most recent turns verbatim.\n */\n async summarize(llm: LLMPlugin): Promise<void> {\n if (this.turns.length <= this.recentTurnsToKeep) return;\n\n const olderTurns = this.turns.slice(0, -this.recentTurnsToKeep);\n const transcript = olderTurns\n .map((t) => `[${t.speaker}]: ${t.text}`)\n .join('\\n');\n\n const summaryPrompt: Message[] = [\n {\n role: 'system',\n content: 'Summarize this conversation concisely, preserving key facts, decisions, and action items.',\n },\n { role: 'user', content: transcript },\n ];\n\n let summaryText = '';\n for await (const chunk of llm.chat(summaryPrompt)) {\n if (chunk.type === 'token' && chunk.token) {\n summaryText += chunk.token;\n }\n }\n\n this.summary = this.summary\n ? `${this.summary}\\n\\n${summaryText}`\n : summaryText;\n\n this.turns = this.turns.slice(-this.recentTurnsToKeep);\n\n log.info(`Summarized ${olderTurns.length} turns, ${this.turns.length} recent turns kept`);\n }\n\n /** Get the full transcript */\n getFullTranscript(): string {\n return this.turns.map((t) => `[${t.speaker}]: ${t.text}`).join('\\n');\n }\n\n /** Reset the context */\n reset(): void {\n this.turns = [];\n this.summary = null;\n }\n}\n","/**\n * SentenceSplitter — buffers streaming LLM tokens into speakable chunks\n * for TTS synthesis.\n *\n * Split strategy:\n * 1. Sentence boundary (.!?) — always split\n * 2. Clause boundary (,;:—) — split if buffer >= MIN_CHUNK chars\n * 3. Word boundary — forced split if buffer >= MAX_CHUNK chars\n */\n\nconst MIN_CHUNK = 20;\nconst MAX_CHUNK = 150;\n\nexport class SentenceSplitter {\n private buffer = '';\n\n /** Add a token and get back any speakable chunks */\n push(token: string): string[] {\n this.buffer += token;\n return this.extractChunks();\n }\n\n /** Flush any remaining text as a final chunk */\n flush(): string | null {\n const text = this.buffer.trim();\n this.buffer = '';\n return text.length > 0 ? text : null;\n }\n\n /** Reset the splitter */\n reset(): void {\n this.buffer = '';\n }\n\n private extractChunks(): string[] {\n const chunks: string[] = [];\n\n while (true) {\n // 1. Sentence boundary (.!?) — split on complete sentences\n const sentenceMatch = this.buffer.match(/[^.!?]*[.!?]\\s*/);\n if (sentenceMatch && sentenceMatch.index !== undefined) {\n const end = sentenceMatch.index + sentenceMatch[0].length;\n const chunk = this.buffer.slice(0, end).trim();\n if (chunk.length >= MIN_CHUNK) {\n chunks.push(chunk);\n this.buffer = this.buffer.slice(end);\n continue;\n }\n }\n\n // 2. Clause boundary (,;:—) if buffer is getting long\n if (this.buffer.length >= MAX_CHUNK) {\n const clauseMatch = this.buffer.match(/[,;:\\u2014]\\s*/);\n if (clauseMatch && clauseMatch.index !== undefined && clauseMatch.index >= MIN_CHUNK) {\n const end = clauseMatch.index + clauseMatch[0].length;\n const chunk = this.buffer.slice(0, end).trim();\n chunks.push(chunk);\n this.buffer = this.buffer.slice(end);\n continue;\n }\n\n // 3. Word boundary — forced split\n const spaceIdx = this.buffer.lastIndexOf(' ', MAX_CHUNK);\n if (spaceIdx >= MIN_CHUNK) {\n const chunk = this.buffer.slice(0, spaceIdx).trim();\n chunks.push(chunk);\n this.buffer = this.buffer.slice(spaceIdx);\n continue;\n }\n }\n\n break;\n }\n\n return chunks;\n }\n}\n","import { createLogger } from '../utils/logger';\n\nconst log = createLogger('TurnDetector');\n\nexport interface TurnDetectorOptions {\n /** Silence duration after final transcription before triggering (default: 800ms) */\n silenceTimeoutMs?: number;\n}\n\nexport class TurnDetector {\n private readonly silenceTimeoutMs: number;\n private silenceTimer: ReturnType<typeof setTimeout> | null = null;\n private _onTurnEnd: (() => void) | null = null;\n private lastFinalText = '';\n\n constructor(options: TurnDetectorOptions = {}) {\n this.silenceTimeoutMs = options.silenceTimeoutMs ?? 800;\n }\n\n /** Set the callback for when a turn ends */\n set onTurnEnd(cb: (() => void) | null) {\n this._onTurnEnd = cb;\n }\n\n /**\n * Feed a transcription result.\n * Returns true if this result represents a completed turn.\n */\n handleTranscription(text: string, isFinal: boolean): boolean {\n this.clearTimer();\n\n if (isFinal && text.trim().length > 0) {\n this.lastFinalText = text;\n\n // Start silence timer — if no new speech, the turn is done\n this.silenceTimer = setTimeout(() => {\n log.debug(`Turn ended after ${this.silenceTimeoutMs}ms silence`);\n this._onTurnEnd?.();\n }, this.silenceTimeoutMs);\n\n return false;\n }\n\n if (!isFinal && text.trim().length > 0) {\n // Interim result — user is still speaking, reset timer\n this.clearTimer();\n }\n\n return false;\n }\n\n /** Force-trigger turn end */\n forceTurnEnd(): void {\n this.clearTimer();\n this._onTurnEnd?.();\n }\n\n /** Reset state */\n reset(): void {\n this.clearTimer();\n this.lastFinalText = '';\n }\n\n private clearTimer(): void {\n if (this.silenceTimer) {\n clearTimeout(this.silenceTimer);\n this.silenceTimer = null;\n }\n }\n}\n","import { createLogger } from '../utils/logger';\n\nconst log = createLogger('BargeIn');\n\nexport class BargeIn {\n private abortController: AbortController | null = null;\n private _interrupted = false;\n private _onInterrupt: (() => void) | null = null;\n\n get interrupted(): boolean {\n return this._interrupted;\n }\n\n /** Set the callback for when barge-in occurs */\n set onInterrupt(cb: (() => void) | null) {\n this._onInterrupt = cb;\n }\n\n /**\n * Create a new AbortController for the current response cycle.\n * Call this at the start of each STT->LLM->TTS cycle.\n */\n startCycle(): AbortSignal {\n this.abortController = new AbortController();\n this._interrupted = false;\n return this.abortController.signal;\n }\n\n /** Trigger barge-in. Called when STT detects speech during agent output. */\n trigger(): void {\n if (this._interrupted) return;\n this._interrupted = true;\n\n log.info('Barge-in detected — cancelling current response');\n\n if (this.abortController) {\n this.abortController.abort();\n this.abortController = null;\n }\n\n this._onInterrupt?.();\n }\n\n /** Reset after the interrupted cycle is cleaned up */\n reset(): void {\n this._interrupted = false;\n this.abortController = null;\n }\n}\n"],"mappings":";;;;;;;;;;AAWA,SAAS,gBAAAA,qBAAoB;;;ACX7B,SAAS,MAAM,iBAAiB,aAAa,mBAAmB;AAGhE,IAAM,MAAM,aAAa,gBAAgB;AAgBlC,IAAM,iBAAN,MAAqB;AAAA,EACjB;AAAA,EACD,cAAkC;AAAA,EAClC,aAAqC;AAAA,EACrC,aAAa;AAAA,EAErB,cAAc;AACZ,SAAK,OAAO,IAAI,KAAK;AAAA,EACvB;AAAA,EAEA,IAAI,YAAqB;AACvB,WAAO,KAAK;AAAA,EACd;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA,EAUA,MAAM,QAAQ,SAA+C;AAC3D,UAAM,EAAE,MAAM,UAAU,WAAW,SAAS,KAAK,IAAI;AAErD,QAAI,KAAK,uBAAuB,QAAQ,SAAS,QAAQ,MAAM;AAE/D,QAAI;AACJ,QAAI;AAEJ,QAAI,QAAQ,SAAS,QAAQ,OAAO;AAElC,YAAM,QAAQ;AACd,cAAQ,QAAQ;AAAA,IAClB,WAAW,QAAQ,UAAU,QAAQ,WAAW;AAE9C,YAAM,EAAE,YAAY,IAAI,MAAM,OAAO,yBAAyB;AAE9D,YAAM,QAAQ,IAAI,YAAY,QAAQ,QAAQ,QAAQ,WAAW;AAAA,QAC/D;AAAA,QACA,MAAM,QAAQ;AAAA,MAChB,CAAC;AACD,YAAM,SAAS;AAAA,QACb,UAAU;AAAA,QACV,MAAM;AAAA,QACN,YAAY;AAAA,QACZ,cAAc;AAAA,QACd,gBAAgB;AAAA,MAClB,CAAC;AAED,cAAQ,MAAM,MAAM,SAAS;AAC7B,YAAM,MAAM,MAAM;AAAA,IACpB,OAAO;AACL,YAAM,IAAI,MAAM,iEAAiE;AAAA,IACnF;AAEA,QAAI,KAAK,YAAY,KAAK,EAAE;AAG5B,UAAM,KAAK,KAAK,QAAQ,OAAO,KAAK,EAAE,eAAe,KAAK,CAAC;AAC3D,SAAK,aAAa;AAElB,QAAI,KAAK,wBAAwB;AAAA,EACnC;AAAA;AAAA;AAAA;AAAA;AAAA,EAMA,MAAM,oBAA0C;AAC9C,QAAI,KAAK,YAAa,QAAO,KAAK;AAGlC,SAAK,cAAc,IAAI,YAAY,MAAO,CAAC;AAC3C,SAAK,aAAa,gBAAgB,iBAAiB,eAAe,KAAK,WAAW;AAElF,UAAM,KAAK,KAAK,iBAAiB,aAAa,KAAK,YAAY;AAAA,MAC7D,MAAM;AAAA,MACN,QAAQ,YAAY;AAAA,IACtB,CAAC;AAED,QAAI,KAAK,uBAAuB;AAChC,WAAO,KAAK;AAAA,EACd;AAAA;AAAA,EAGA,MAAM,aAA4B;AAChC,QAAI,CAAC,KAAK,WAAY;AAEtB,QAAI,KAAK,YAAY;AACnB,YAAM,KAAK,KAAK,iBAAiB,eAAe,KAAK,UAAU;AAC/D,WAAK,aAAa;AAAA,IACpB;AAEA,QAAI,KAAK,aAAa;AACpB,WAAK,YAAY,QAAQ;AACzB,WAAK,cAAc;AAAA,IACrB;AAEA,UAAM,KAAK,KAAK,WAAW;AAC3B,SAAK,aAAa;AAElB,QAAI,KAAK,wBAAwB;AAAA,EACnC;AACF;;;ACxHA,IAAMC,OAAM,aAAa,YAAY;AAE9B,IAAM,aAAN,MAAiB;AAAA,EACb;AAAA,EACD;AAAA,EACA,UAAU;AAAA,EACV,aAAa;AAAA,EAErB,YAAY,OAAyB,qBAA6B;AAChE,SAAK,sBAAsB;AAE3B,SAAK,SAAS,MAAM,aAAa,MAAO,CAAC;AACzC,IAAAA,KAAI,KAAK,2BAA2B,mBAAmB,eAAe,MAAM,GAAG,GAAG;AAAA,EACpF;AAAA,EAEA,IAAI,SAAkB;AACpB,WAAO,KAAK;AAAA,EACd;AAAA;AAAA;AAAA;AAAA;AAAA,EAMA,OAAO,SAAiC;AACtC,qBAAiB,SAAS,KAAK,QAAQ;AACrC,UAAI,KAAK,QAAS;AAClB,WAAK;AACL,UAAI,KAAK,eAAe,KAAK,KAAK,aAAa,QAAQ,GAAG;AACxD,QAAAA,KAAI,KAAK,IAAI,KAAK,mBAAmB,YAAY,KAAK,UAAU,EAAE;AAAA,MACpE;AACA,YAAM,MAAM,SAAS;AAAA,IACvB;AACA,IAAAA,KAAI,KAAK,IAAI,KAAK,mBAAmB,kCAAkC,KAAK,UAAU,GAAG;AAAA,EAC3F;AAAA;AAAA,EAGA,OAAO,cAA0C;AAC/C,qBAAiB,SAAS,KAAK,QAAQ;AACrC,UAAI,KAAK,QAAS;AAClB,YAAM;AAAA,IACR;AAAA,EACF;AAAA,EAEA,QAAc;AACZ,QAAI,KAAK,QAAS;AAClB,SAAK,UAAU;AACf,SAAK,OAAO,MAAM;AAClB,IAAAA,KAAI,MAAM,sCAAsC,KAAK,mBAAmB,GAAG;AAAA,EAC7E;AACF;;;ACpDA,SAAsB,kBAAkB;AACxC,SAAS,eAAe,gBAAgB,YAAY,iBAAiB;AACrE,SAAS,YAAY;AAGrB,IAAMC,OAAM,aAAa,aAAa;AAGtC,IAAM,cAAc;AACpB,IAAM,WAAW;AACjB,IAAM,oBAAoB;AAC1B,IAAM,oBAAqB,cAAc,oBAAqB;AAG9D,IAAM,UAAU,IAAI,WAAW,iBAAiB;AAEzC,IAAM,cAAN,MAAkB;AAAA,EACf;AAAA,EACA,WAAW;AAAA,EACX,cAAc;AAAA,EACd,WAAW;AAAA,EACX,kBAAyD;AAAA;AAAA,EAGxD;AAAA,EACD;AAAA;AAAA,EAGR,UAAyB;AAAA,EACjB,cAAc;AAAA,EAEtB,YAAY,QAAqB;AAC/B,SAAK,SAAS;AACd,QAAI,OAAO,OAAO;AAChB,WAAK,YAAY,QAAQ,QAAQ;AAAA,IACnC,OAAO;AACL,WAAK,YAAY,IAAI,QAAQ,CAAC,YAAY;AAAE,aAAK,gBAAgB;AAAA,MAAS,CAAC;AAAA,IAC7E;AAAA,EACF;AAAA,EAEA,IAAI,UAAmB;AACrB,WAAO,KAAK;AAAA,EACd;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA,EAOA,gBAAsB;AACpB,SAAK,cAAc;AAAA,EACrB;AAAA;AAAA,EAGA,cAAoB;AAClB,SAAK,cAAc;AAAA,EACrB;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA,EAUA,eAAqB;AACnB,QAAI,KAAK,gBAAiB;AAE1B,UAAM,iBAAiB,MAAM;AAC3B,MAAAA,KAAI,MAAM,8EAAyE;AAInF,eAAS,IAAI,GAAG,IAAI,IAAI,KAAK;AAC3B,aAAK,iBAAiB;AAAA,MACxB;AACA,WAAK,gBAAgB;AAErB,WAAK,kBAAkB,YAAY,MAAM;AACvC,YAAI,CAAC,KAAK,YAAY,CAAC,KAAK,eAAe,CAAC,KAAK,UAAU;AACzD,eAAK,iBAAiB;AAAA,QACxB;AAAA,MACF,GAAG,GAAI;AAAA,IACT;AAEA,QAAI,KAAK,OAAO,OAAO;AACrB,qBAAe;AAAA,IACjB,OAAO;AACL,MAAAA,KAAI,MAAM,4DAA4D;AACtE,WAAK,OAAO,UAAU,MAAM,eAAe;AAAA,IAC7C;AAAA,EACF;AAAA,EAEQ,mBAAyB;AAC/B,UAAM,QAAQ,IAAI,WAAW,SAAS,aAAa,UAAU,iBAAiB;AAC9E,SAAK,OAAO,aAAa,KAAK,EAAE,MAAM,CAAC,QAAQ;AAC7C,MAAAA,KAAI,KAAK,iCAAiC,GAAG;AAAA,IAC/C,CAAC;AAAA,EACH;AAAA;AAAA;AAAA;AAAA;AAAA,EAMA,MAAM,YAAY,OAA8B;AAC9C,SAAK,WAAW;AAChB,QAAI;AACF,YAAM,KAAK,YAAY,KAAK;AAAA,IAC9B,UAAE;AACA,WAAK,WAAW;AAAA,IAClB;AAAA,EACF;AAAA;AAAA;AAAA;AAAA;AAAA,EAMA,MAAM,YACJ,QACA,QACe;AACf,SAAK,WAAW;AAChB,UAAM,cAAc,YAAY,IAAI;AACpC,QAAI,aAAa;AACjB,QAAI,aAAa;AAEjB,IAAAA,KAAI,MAAM,sBAAsB;AAGhC,UAAM,YAA6B,KAAK,UAAU,CAAC,IAAI;AAEvD,QAAI;AACF,uBAAiB,SAAS,QAAQ;AAChC,YAAI,QAAQ,SAAS;AACnB,UAAAA,KAAI,MAAM,gCAAgC,UAAU,aAAa,YAAY,IAAI,IAAI,aAAa,QAAQ,CAAC,CAAC,IAAI;AAChH;AAAA,QACF;AACA;AACA,sBAAc,MAAM;AACpB,mBAAW,KAAK,OAAO,KAAK,KAAK,CAAC;AAClC,cAAM,KAAK,YAAY,KAAK;AAAA,MAC9B;AAAA,IACF,UAAE;AACA,WAAK,WAAW;AAChB,YAAM,UAAU,YAAY,IAAI,IAAI;AACpC,YAAM,kBAAmB,aAAa,IAAK,cAAc;AACzD,MAAAA,KAAI;AAAA,QACF,4BAAuB,UAAU,YAAY,UAAU,iBAC9C,gBAAgB,QAAQ,CAAC,CAAC,YAAY,QAAQ,QAAQ,CAAC,CAAC;AAAA,MACnE;AAGA,UAAI,aAAa,UAAU,SAAS,KAAK,KAAK,SAAS;AACrD,YAAI;AACF,cAAI,CAAC,WAAW,KAAK,OAAO,EAAG,WAAU,KAAK,SAAS,EAAE,WAAW,KAAK,CAAC;AAC1E,gBAAM,WAAW,KAAK,KAAK,SAAS,WAAW,EAAE,KAAK,WAAW,MAAM;AACvE,mBAAS,UAAU,WAAW,WAAW;AACzC,UAAAA,KAAI,KAAK,iCAAiC,QAAQ,EAAE;AAAA,QACtD,SAAS,KAAK;AACZ,UAAAA,KAAI,KAAK,yCAAyC,GAAG;AAAA,QACvD;AAAA,MACF;AAAA,IACF;AAAA,EACF;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA,EAOA,MAAc,YAAY,OAA8B;AAGtD,UAAM,UAAU,OAAO,MAAM,MAAM,UAAU;AAC7C,UAAM,KAAK,OAAO;AAClB,UAAM,UAAU,IAAI;AAAA,MAClB,QAAQ;AAAA,MACR,QAAQ;AAAA,MACR,QAAQ,aAAa;AAAA,IACvB;AAEA,QAAI,SAAS;AACb,WAAO,SAAS,QAAQ,QAAQ;AAC9B,YAAM,MAAM,KAAK,IAAI,SAAS,mBAAmB,QAAQ,MAAM;AAC/D,YAAM,eAAe,QAAQ,SAAS,QAAQ,GAAG;AAEjD,YAAM,QAAQ,IAAI;AAAA,QAChB;AAAA,QACA;AAAA,QACA;AAAA,QACA,aAAa;AAAA,MACf;AAEA,YAAM,KAAK,OAAO,aAAa,KAAK;AAKpC,UAAI,aAAa,WAAW,mBAAmB;AAC7C,cAAM,MAAM,iBAAiB;AAAA,MAC/B;AAEA,eAAS;AAAA,IACX;AAAA,EACF;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA,EAOA,MAAM,aAAa,YAAmC;AACpD,UAAM,aAAa,KAAK,KAAK,aAAa,iBAAiB;AAC3D,aAAS,IAAI,GAAG,IAAI,YAAY,KAAK;AACnC,YAAM,QAAQ,IAAI,WAAW,SAAS,aAAa,UAAU,iBAAiB;AAC9E,YAAM,KAAK,OAAO,aAAa,KAAK;AACpC,YAAM,MAAM,iBAAiB;AAAA,IAC/B;AAAA,EACF;AAAA;AAAA,EAGA,QAAc;AACZ,SAAK,OAAO,MAAM;AAClB,SAAK,WAAW;AAAA,EAClB;AAAA;AAAA,EAGA,OAAa;AACX,SAAK,WAAW;AAChB,QAAI,KAAK,iBAAiB;AACxB,oBAAc,KAAK,eAAe;AAClC,WAAK,kBAAkB;AAAA,IACzB;AAAA,EACF;AACF;AAEA,SAAS,MAAM,IAA2B;AACxC,SAAO,IAAI,QAAQ,CAAC,YAAY,WAAW,SAAS,EAAE,CAAC;AACzD;AAGA,SAAS,SAAS,UAAkB,WAAqB,YAA0B;AACjF,QAAM,WAAW,UAAU,OAAO,CAAC,KAAK,MAAM,MAAM,EAAE,YAAY,CAAC;AACnE,QAAM,SAAS,OAAO,MAAM,EAAE;AAC9B,SAAO,MAAM,QAAQ,CAAC;AACtB,SAAO,cAAc,KAAK,UAAU,CAAC;AACrC,SAAO,MAAM,QAAQ,CAAC;AACtB,SAAO,MAAM,QAAQ,EAAE;AACvB,SAAO,cAAc,IAAI,EAAE;AAC3B,SAAO,cAAc,GAAG,EAAE;AAC1B,SAAO,cAAc,GAAG,EAAE;AAC1B,SAAO,cAAc,YAAY,EAAE;AACnC,SAAO,cAAc,aAAa,GAAG,EAAE;AACvC,SAAO,cAAc,GAAG,EAAE;AAC1B,SAAO,cAAc,IAAI,EAAE;AAC3B,SAAO,MAAM,QAAQ,EAAE;AACvB,SAAO,cAAc,UAAU,EAAE;AAEjC,gBAAc,UAAU,MAAM;AAC9B,aAAW,SAAS,WAAW;AAC7B,mBAAe,UAAU,KAAK;AAAA,EAChC;AACF;;;AC5PA,SAAS,oBAAoB;;;ACR7B,IAAMC,OAAM,aAAa,gBAAgB;AAGzC,SAAS,eAAe,MAAsB;AAC5C,SAAO,KAAK,KAAK,KAAK,SAAS,CAAC;AAClC;AAkBO,IAAM,iBAAN,MAAqB;AAAA,EACT;AAAA,EACA;AAAA,EACA;AAAA,EAET,QAAgB,CAAC;AAAA,EACjB,UAAyB;AAAA,EAEjC,YAAY,SAAgC;AAC1C,SAAK,eAAe,QAAQ;AAC5B,SAAK,mBAAmB,QAAQ,oBAAoB;AACpD,SAAK,oBAAoB,QAAQ,qBAAqB;AAAA,EACxD;AAAA;AAAA,EAGA,YAAY,SAAiB,MAAoB;AAC/C,SAAK,MAAM,KAAK;AAAA,MACd;AAAA,MACA;AAAA,MACA,SAAS;AAAA,MACT,WAAW,KAAK,IAAI;AAAA,IACtB,CAAC;AAAA,EACH;AAAA;AAAA,EAGA,aAAa,MAAoB;AAC/B,SAAK,MAAM,KAAK;AAAA,MACd,SAAS;AAAA,MACT;AAAA,MACA,SAAS;AAAA,MACT,WAAW,KAAK,IAAI;AAAA,IACtB,CAAC;AAAA,EACH;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA,EAaA,cAAc,eAAmC;AAC/C,UAAM,WAAsB,CAAC;AAG7B,aAAS,KAAK,EAAE,MAAM,UAAU,SAAS,KAAK,aAAa,CAAC;AAG5D,QAAI,eAAe;AACjB,eAAS,KAAK;AAAA,QACZ,MAAM;AAAA,QACN,SAAS;AAAA,EAA8C,aAAa;AAAA,MACtE,CAAC;AAAA,IACH;AAGA,QAAI,KAAK,SAAS;AAChB,eAAS,KAAK;AAAA,QACZ,MAAM;AAAA,QACN,SAAS;AAAA,EAAiC,KAAK,OAAO;AAAA,MACxD,CAAC;AAAA,IACH;AAGA,UAAM,iBAAiB,KAAK,UACxB,KAAK,MAAM,MAAM,CAAC,KAAK,iBAAiB,IACxC,KAAK;AAET,eAAW,QAAQ,gBAAgB;AACjC,UAAI,KAAK,SAAS;AAChB,iBAAS,KAAK,EAAE,MAAM,aAAa,SAAS,KAAK,KAAK,CAAC;AAAA,MACzD,OAAO;AACL,iBAAS,KAAK;AAAA,UACZ,MAAM;AAAA,UACN,SAAS,IAAI,KAAK,OAAO,MAAM,KAAK,IAAI;AAAA,QAC1C,CAAC;AAAA,MACH;AAAA,IACF;AAEA,WAAO;AAAA,EACT;AAAA;AAAA,EAGA,kBAA2B;AACzB,UAAM,cAAc,KAAK,MAAM;AAAA,MAC7B,CAAC,KAAK,MAAM,MAAM,eAAe,EAAE,IAAI,IAAI;AAAA,MAC3C,eAAe,KAAK,YAAY;AAAA,IAClC;AACA,WAAO,cAAc,KAAK;AAAA,EAC5B;AAAA;AAAA;AAAA;AAAA;AAAA,EAMA,MAAM,UAAU,KAA+B;AAC7C,QAAI,KAAK,MAAM,UAAU,KAAK,kBAAmB;AAEjD,UAAM,aAAa,KAAK,MAAM,MAAM,GAAG,CAAC,KAAK,iBAAiB;AAC9D,UAAM,aAAa,WAChB,IAAI,CAAC,MAAM,IAAI,EAAE,OAAO,MAAM,EAAE,IAAI,EAAE,EACtC,KAAK,IAAI;AAEZ,UAAM,gBAA2B;AAAA,MAC/B;AAAA,QACE,MAAM;AAAA,QACN,SAAS;AAAA,MACX;AAAA,MACA,EAAE,MAAM,QAAQ,SAAS,WAAW;AAAA,IACtC;AAEA,QAAI,cAAc;AAClB,qBAAiB,SAAS,IAAI,KAAK,aAAa,GAAG;AACjD,UAAI,MAAM,SAAS,WAAW,MAAM,OAAO;AACzC,uBAAe,MAAM;AAAA,MACvB;AAAA,IACF;AAEA,SAAK,UAAU,KAAK,UAChB,GAAG,KAAK,OAAO;AAAA;AAAA,EAAO,WAAW,KACjC;AAEJ,SAAK,QAAQ,KAAK,MAAM,MAAM,CAAC,KAAK,iBAAiB;AAErD,IAAAA,KAAI,KAAK,cAAc,WAAW,MAAM,WAAW,KAAK,MAAM,MAAM,oBAAoB;AAAA,EAC1F;AAAA;AAAA,EAGA,oBAA4B;AAC1B,WAAO,KAAK,MAAM,IAAI,CAAC,MAAM,IAAI,EAAE,OAAO,MAAM,EAAE,IAAI,EAAE,EAAE,KAAK,IAAI;AAAA,EACrE;AAAA;AAAA,EAGA,QAAc;AACZ,SAAK,QAAQ,CAAC;AACd,SAAK,UAAU;AAAA,EACjB;AACF;;;AC7JA,IAAM,YAAY;AAClB,IAAM,YAAY;AAEX,IAAM,mBAAN,MAAuB;AAAA,EACpB,SAAS;AAAA;AAAA,EAGjB,KAAK,OAAyB;AAC5B,SAAK,UAAU;AACf,WAAO,KAAK,cAAc;AAAA,EAC5B;AAAA;AAAA,EAGA,QAAuB;AACrB,UAAM,OAAO,KAAK,OAAO,KAAK;AAC9B,SAAK,SAAS;AACd,WAAO,KAAK,SAAS,IAAI,OAAO;AAAA,EAClC;AAAA;AAAA,EAGA,QAAc;AACZ,SAAK,SAAS;AAAA,EAChB;AAAA,EAEQ,gBAA0B;AAChC,UAAM,SAAmB,CAAC;AAE1B,WAAO,MAAM;AAEX,YAAM,gBAAgB,KAAK,OAAO,MAAM,iBAAiB;AACzD,UAAI,iBAAiB,cAAc,UAAU,QAAW;AACtD,cAAM,MAAM,cAAc,QAAQ,cAAc,CAAC,EAAE;AACnD,cAAM,QAAQ,KAAK,OAAO,MAAM,GAAG,GAAG,EAAE,KAAK;AAC7C,YAAI,MAAM,UAAU,WAAW;AAC7B,iBAAO,KAAK,KAAK;AACjB,eAAK,SAAS,KAAK,OAAO,MAAM,GAAG;AACnC;AAAA,QACF;AAAA,MACF;AAGA,UAAI,KAAK,OAAO,UAAU,WAAW;AACnC,cAAM,cAAc,KAAK,OAAO,MAAM,gBAAgB;AACtD,YAAI,eAAe,YAAY,UAAU,UAAa,YAAY,SAAS,WAAW;AACpF,gBAAM,MAAM,YAAY,QAAQ,YAAY,CAAC,EAAE;AAC/C,gBAAM,QAAQ,KAAK,OAAO,MAAM,GAAG,GAAG,EAAE,KAAK;AAC7C,iBAAO,KAAK,KAAK;AACjB,eAAK,SAAS,KAAK,OAAO,MAAM,GAAG;AACnC;AAAA,QACF;AAGA,cAAM,WAAW,KAAK,OAAO,YAAY,KAAK,SAAS;AACvD,YAAI,YAAY,WAAW;AACzB,gBAAM,QAAQ,KAAK,OAAO,MAAM,GAAG,QAAQ,EAAE,KAAK;AAClD,iBAAO,KAAK,KAAK;AACjB,eAAK,SAAS,KAAK,OAAO,MAAM,QAAQ;AACxC;AAAA,QACF;AAAA,MACF;AAEA;AAAA,IACF;AAEA,WAAO;AAAA,EACT;AACF;;;AC1EA,IAAMC,OAAM,aAAa,cAAc;AAOhC,IAAM,eAAN,MAAmB;AAAA,EACP;AAAA,EACT,eAAqD;AAAA,EACrD,aAAkC;AAAA,EAClC,gBAAgB;AAAA,EAExB,YAAY,UAA+B,CAAC,GAAG;AAC7C,SAAK,mBAAmB,QAAQ,oBAAoB;AAAA,EACtD;AAAA;AAAA,EAGA,IAAI,UAAU,IAAyB;AACrC,SAAK,aAAa;AAAA,EACpB;AAAA;AAAA;AAAA;AAAA;AAAA,EAMA,oBAAoB,MAAc,SAA2B;AAC3D,SAAK,WAAW;AAEhB,QAAI,WAAW,KAAK,KAAK,EAAE,SAAS,GAAG;AACrC,WAAK,gBAAgB;AAGrB,WAAK,eAAe,WAAW,MAAM;AACnC,QAAAA,KAAI,MAAM,oBAAoB,KAAK,gBAAgB,YAAY;AAC/D,aAAK,aAAa;AAAA,MACpB,GAAG,KAAK,gBAAgB;AAExB,aAAO;AAAA,IACT;AAEA,QAAI,CAAC,WAAW,KAAK,KAAK,EAAE,SAAS,GAAG;AAEtC,WAAK,WAAW;AAAA,IAClB;AAEA,WAAO;AAAA,EACT;AAAA;AAAA,EAGA,eAAqB;AACnB,SAAK,WAAW;AAChB,SAAK,aAAa;AAAA,EACpB;AAAA;AAAA,EAGA,QAAc;AACZ,SAAK,WAAW;AAChB,SAAK,gBAAgB;AAAA,EACvB;AAAA,EAEQ,aAAmB;AACzB,QAAI,KAAK,cAAc;AACrB,mBAAa,KAAK,YAAY;AAC9B,WAAK,eAAe;AAAA,IACtB;AAAA,EACF;AACF;;;ACnEA,IAAMC,OAAM,aAAa,SAAS;AAE3B,IAAM,UAAN,MAAc;AAAA,EACX,kBAA0C;AAAA,EAC1C,eAAe;AAAA,EACf,eAAoC;AAAA,EAE5C,IAAI,cAAuB;AACzB,WAAO,KAAK;AAAA,EACd;AAAA;AAAA,EAGA,IAAI,YAAY,IAAyB;AACvC,SAAK,eAAe;AAAA,EACtB;AAAA;AAAA;AAAA;AAAA;AAAA,EAMA,aAA0B;AACxB,SAAK,kBAAkB,IAAI,gBAAgB;AAC3C,SAAK,eAAe;AACpB,WAAO,KAAK,gBAAgB;AAAA,EAC9B;AAAA;AAAA,EAGA,UAAgB;AACd,QAAI,KAAK,aAAc;AACvB,SAAK,eAAe;AAEpB,IAAAA,KAAI,KAAK,sDAAiD;AAE1D,QAAI,KAAK,iBAAiB;AACxB,WAAK,gBAAgB,MAAM;AAC3B,WAAK,kBAAkB;AAAA,IACzB;AAEA,SAAK,eAAe;AAAA,EACtB;AAAA;AAAA,EAGA,QAAc;AACZ,SAAK,eAAe;AACpB,SAAK,kBAAkB;AAAA,EACzB;AACF;;;AJhBA,IAAMC,OAAM,aAAa,UAAU;AAQnC,IAAM,iBAAiB;AAEvB,SAASC,OAAM,IAA2B;AACxC,SAAO,IAAI,QAAQ,CAAC,YAAY,WAAW,SAAS,EAAE,CAAC;AACzD;AAOA,SAAS,YACP,KACA,MACA,QAC8B;AAC9B,QAAM,SAAmB,CAAC;AAC1B,MAAI,OAAO;AACX,MAAI,QAAiB;AACrB,MAAI,OAA4B;AAEhC,QAAM,SAAS,MAAM;AAAE,QAAI,MAAM;AAAE,YAAM,IAAI;AAAM,aAAO;AAAM,QAAE;AAAA,IAAG;AAAA,EAAE;AAGvE,QAAM,YAAY;AAChB,QAAI;AACF,YAAM,SAAS,IAAI,WAAW,MAAM,MAAM;AAC1C,uBAAiB,SAAS,QAAQ;AAChC,YAAI,QAAQ,QAAS;AACrB,eAAO,KAAK,KAAK;AACjB,eAAO;AAAA,MACT;AAAA,IACF,SAAS,GAAY;AACnB,UAAI,EAAE,aAAa,SAAS,EAAE,SAAS,cAAe,SAAQ;AAAA,IAChE,UAAE;AACA,aAAO;AACP,aAAO;AAAA,IACT;AAAA,EACF,GAAG;AAGH,SAAO,mBAAmB;AACxB,QAAI,QAAQ;AACZ,WAAO,MAAM;AACX,UAAI,QAAQ,QAAS;AACrB,UAAI,MAAO,OAAM;AACjB,UAAI,QAAQ,OAAO,QAAQ;AACzB,cAAM,OAAO,OAAO;AACpB;AAAA,MACF;AACA,UAAI,KAAM;AACV,YAAM,IAAI,QAAc,CAAC,MAAM;AAAE,eAAO;AAAA,MAAG,CAAC;AAAA,IAC9C;AAAA,EACF;AACF;AAEO,IAAM,WAAN,cAAuB,aAAa;AAAA,EACxB;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA;AAAA,EAGT,UAAU,MAAsB;AACtC,WAAO,KAAK,KAAK,YAAY,KAAK,IAAI,UAAU,IAAI,IAAI;AAAA,EAC1D;AAAA;AAAA,EAGQ,aAAa,oBAAI,IAAuB;AAAA,EAExC,cAAc;AAAA,EACd,WAAW;AAAA,EACX,cAA0B;AAAA;AAAA,EAE1B,cAAwD;AAAA,EAEhE,YAAY,SAA0B;AACpC,UAAM;AACN,SAAK,MAAM,QAAQ;AACnB,SAAK,MAAM,QAAQ;AACnB,SAAK,MAAM,QAAQ;AACnB,SAAK,cAAc,QAAQ;AAC3B,SAAK,cAAc,QAAQ,eAAe;AAC1C,SAAK,aAAa,QAAQ,aAAa,aAAa,YAAY;AAChE,SAAK,gBAAgB,QAAQ,gBAAgB,CAAC,GAAG,IAAI,CAAC,MAAM,EAAE,YAAY,CAAC;AAC3E,SAAK,gBAAgB,QAAQ;AAC7B,SAAK,SAAS,QAAQ;AACtB,SAAK,QAAQ,QAAQ;AACrB,SAAK,UAAU,IAAI,eAAe;AAAA,MAChC,cAAc,QAAQ;AAAA,MACtB,kBAAkB,QAAQ;AAAA,IAC5B,CAAC;AACD,SAAK,eAAe,IAAI,aAAa;AAAA,MACnC,kBAAkB,QAAQ;AAAA,IAC5B,CAAC;AACD,SAAK,UAAU,IAAI,QAAQ;AAC3B,SAAK,WAAW,IAAI,iBAAiB;AAErC,SAAK,aAAa,YAAY,MAAM;AAAA,IAAC;AAErC,SAAK,QAAQ,cAAc,MAAM;AAC/B,WAAK,YAAY,MAAM;AACvB,WAAK,SAAS,MAAM;AACpB,WAAK,cAAc,MAAM;AAAA,IAC3B;AAGA,SAAK,oBAAoB,KAAK,KAAK,SAC/B,KAAK,IAAI,OAAO,EAAE,MAAM,CAAC,QAAiB;AAAE,MAAAD,KAAI,KAAK,kCAAkC,GAAG;AAAA,IAAG,CAAC,IAC9F,QAAQ,QAAQ;AACpB,SAAK,oBAAoB,KAAK,IAAI,SAC9B,KAAK,IAAI,OAAO,QAAQ,YAAY,EAAE,MAAM,CAAC,QAAiB;AAAE,MAAAA,KAAI,KAAK,kCAAkC,GAAG;AAAA,IAAG,CAAC,IAClH,QAAQ,QAAQ;AACpB,SAAK,qBAAqB,KAAK,YAAY;AAC3C,SAAK,iBAAiB,QAAQ,IAAI,CAAC,KAAK,mBAAmB,KAAK,mBAAmB,KAAK,kBAAkB,CAAC,EAAE,KAAK,MAAM;AAAA,IAAC,CAAC;AAAA,EAC5H;AAAA,EAEiB;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EAEjB,IAAI,aAAsB;AACxB,WAAO,KAAK;AAAA,EACd;AAAA,EAEA,IAAI,UAAmB;AACrB,WAAO,KAAK;AAAA,EACd;AAAA,EAEA,IAAI,aAAyB;AAC3B,WAAO,KAAK;AAAA,EACd;AAAA,EAEQ,cAAc,OAAyB;AAC7C,QAAI,KAAK,gBAAgB,OAAO;AAC9B,WAAK,cAAc;AACnB,WAAK,KAAK,cAAc,KAAK;AAAA,IAC/B;AAAA,EACF;AAAA,EAEA,eAAe,UAA6B;AAC1C,UAAM,WAAW,KAAK,WAAW,IAAI,QAAQ;AAC7C,QAAI,UAAU;AACZ,eAAS,MAAM;AACf,WAAK,WAAW,OAAO,QAAQ;AAC/B,MAAAA,KAAI,KAAK,6BAA6B,QAAQ,GAAG;AAAA,IACnD;AAEA,UAAM,SAAS,KAAK,IAAI,aAAa;AACrC,SAAK,WAAW,IAAI,UAAU,MAAM;AACpC,SAAK,WAAW;AAEhB,WAAO,GAAG,iBAAiB,CAAC,WAAW;AACrC,WAAK,oBAAoB,UAAU,MAAM;AAAA,IAC3C,CAAC;AAED,WAAO,GAAG,SAAS,CAAC,UAAU;AAC5B,MAAAA,KAAI,MAAM,iBAAiB,QAAQ,KAAK,KAAK;AAC7C,WAAK,KAAK,SAAS,KAAK;AAAA,IAC1B,CAAC;AAED,IAAAA,KAAI,KAAK,uCAAuC,QAAQ,GAAG;AAC3D,WAAO;AAAA,EACT;AAAA,EAEA,MAAM,kBAAkB,UAAiC;AACvD,UAAM,SAAS,KAAK,WAAW,IAAI,QAAQ;AAC3C,QAAI,QAAQ;AACV,YAAM,OAAO,MAAM;AACnB,WAAK,WAAW,OAAO,QAAQ;AAC/B,MAAAA,KAAI,KAAK,uCAAuC,QAAQ,GAAG;AAAA,IAC7D;AAAA,EACF;AAAA,EAEA,MAAM,OAAsB;AAC1B,SAAK,WAAW;AAChB,SAAK,aAAa,MAAM;AACxB,SAAK,QAAQ,MAAM;AACnB,SAAK,SAAS,MAAM;AAEpB,eAAW,CAAC,EAAE,MAAM,KAAK,KAAK,YAAY;AACxC,YAAM,OAAO,MAAM;AAAA,IACrB;AACA,SAAK,WAAW,MAAM;AAEtB,SAAK,KAAK,QAAQ;AAElB,IAAAA,KAAI,KAAK,kBAAkB;AAAA,EAC7B;AAAA,EAEA,oBAAoC;AAClC,WAAO,KAAK;AAAA,EACd;AAAA;AAAA,EAGA,eAAe,UAAkB,SAA4C;AAC3E,eAAW,CAAC,UAAU,MAAM,KAAK,KAAK,YAAY;AAChD,UAAI,OAAO,aAAa;AACtB,eAAO,YAAY,UAAU,OAAO;AACpC,QAAAA,KAAI,KAAK,uBAAkB,QAAQ,GAAG,SAAS,eAAe,eAAe,EAAE,SAAS,QAAQ,GAAG;AAAA,MACrG;AAAA,IACF;AAAA,EACF;AAAA,EAEQ,cAAc;AAAA,EACd,kBAAkB;AAAA,EAE1B,MAAc,oBAAoB,SAAiB,QAA4C;AAC7F,SAAK,KAAK,iBAAiB,EAAE,GAAG,QAAQ,QAAQ,CAAC;AAGjD,QAAI,CAAC,OAAO,SAAS;AACnB,WAAK,cAAc,WAAW;AAAA,IAChC;AAEA,QAAI,KAAK,YAAY,WAAW,OAAO,KAAK,KAAK,EAAE,SAAS,GAAG;AAC7D,WAAK,QAAQ,QAAQ;AAAA,IACvB;AAEA,QAAI,OAAO,WAAW,OAAO,KAAK,KAAK,GAAG;AACxC,YAAM,OAAO,OAAO,KAAK,KAAK;AAC9B,WAAK,cAAc,YAAY,IAAI;AACnC,WAAK,kBAAkB,OAAO,eAAe;AAG7C,WAAK,QAAQ,UAAU,SAAS,MAAM,KAAK;AAE3C,UAAI,MAAM,KAAK,cAAc,SAAS,IAAI,GAAG;AAC3C,aAAK,YAAY,SAAS,IAAI;AAAA,MAChC,OAAO;AACL,QAAAA,KAAI,KAAK,sBAAsB,OAAO,OAAO,KAAK,MAAM,GAAG,EAAE,CAAC,WAAW,KAAK,WAAW,GAAG;AAC5F,aAAK,cAAc,MAAM;AAAA,MAC3B;AAAA,IACF,WAAW,OAAO,SAAS;AAEzB,WAAK,cAAc,MAAM;AAAA,IAC3B;AAAA,EACF;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA,EAOA,MAAc,cAAc,SAAiB,MAAgC;AAC3E,QAAI,KAAK,gBAAgB,SAAU,QAAO;AAG1C,UAAM,QAAQ,KAAK,YAAY;AAC/B,UAAM,YAAY,MAAM,SAAS,KAAK,SAAS,KAC7C,KAAK,aAAa,KAAK,CAAC,MAAM,MAAM,SAAS,CAAC,CAAC;AAEjD,QAAI,CAAC,UAAW,QAAO;AAGvB,QAAI,KAAK,eAAe;AACtB,aAAO,KAAK,cAAc,SAAS,IAAI;AAAA,IACzC;AAEA,WAAO;AAAA,EACT;AAAA,EAEA,MAAc,YAAY,SAAiB,MAA6B;AACtE,QAAI,KAAK,aAAa;AACpB,MAAAA,KAAI,KAAK,6CAA6C,IAAI,GAAG;AAC7D,WAAK,cAAc,EAAE,SAAS,KAAK;AACnC,WAAK,QAAQ,QAAQ;AACrB;AAAA,IACF;AAEA,SAAK,cAAc;AACnB,UAAM,KAAK;AAGX,UAAM,aAAa,KAAK;AACxB,UAAM,cAAc,KAAK;AACzB,QAAI,iBAAiB;AACrB,QAAI,iBAAiB;AACrB,QAAI,oBAAoB;AAExB,IAAAA,KAAI,KAAK,yBAAyB,OAAO,MAAM,IAAI,EAAE;AAErD,QAAI;AACF,WAAK,QAAQ,YAAY,SAAS,IAAI;AAEtC,UAAI,KAAK,QAAQ,gBAAgB,GAAG;AAClC,cAAM,KAAK,QAAQ,UAAU,KAAK,GAAG;AAAA,MACvC;AAEA,YAAM,SAAS,KAAK,QAAQ,WAAW;AAGvC,UAAI,gBAAgB;AACpB,UAAI,KAAK,QAAQ;AACf,YAAI;AACF,0BAAgB,MAAM,KAAK,OAAO,eAAe,IAAI;AAAA,QACvD,SAAS,KAAK;AACZ,UAAAA,KAAI,KAAK,yBAAyB,GAAG;AAAA,QACvC;AAAA,MACF;AAEA,YAAM,WAAW,KAAK,QAAQ,cAAc,iBAAiB,MAAS;AACtE,UAAI,eAAe;AAEnB,WAAK,cAAc,UAAU;AAG7B,YAAM,gBAA0B,CAAC;AACjC,UAAI,eAAe;AACnB,UAAI,eAAoC;AAExC,YAAM,OAAO,MAAM;AAAE,uBAAe;AAAA,MAAG;AAGvC,UAAI,kBAAkB;AACtB,YAAM,eAAe,CAACE,UAAiB;AACrC,YAAI,OAAO,QAAS;AACpB,YAAI,iBAAiB;AACnB,2BAAiB,YAAY,IAAI;AACjC,4BAAkB;AAClB,UAAAF,KAAI,KAAK,oBAAoB,iBAAiB,YAAY,QAAQ,CAAC,CAAC,cAASE,MAAK,MAAM,GAAG,EAAE,CAAC,GAAG;AAAA,QACnG;AACA,sBAAc,KAAKA,KAAI;AACvB,aAAK;AAAA,MACP;AAGA,YAAM,kBAAkB;AAExB,UAAI,kBAAkB;AAEtB,YAAM,WAAW,YAAY;AAC3B,cAAM,cAAc,KAAK,KAAK;AAE9B,iBAAS,UAAU,GAAG,WAAW,iBAAiB,WAAW;AAC3D,cAAI,OAAO,QAAS;AAEpB,cAAI,UAAU,GAAG;AACf,YAAAF,KAAI,KAAK,aAAa,OAAO,IAAI,eAAe,KAAK;AACrD,iBAAK,SAAS,MAAM;AAAA,UACtB;AAEA,cAAI,eAAe;AACnB,gBAAM,SAAgD,CAAC;AAEvD,gBAAM,gBAAgB,MAAM;AAC1B,gBAAI,OAAO,WAAW,EAAG;AAEzB,kBAAM,WAAW,OACd;AAAA,cAAI,CAAC,MACJ,EAAE,SAAS,cACP,mBAAmB,EAAE,IAAI,KAAK,EAAE,IAAI,YACpC,EAAE;AAAA,YACR,EACC,KAAK,GAAG;AACX,mBAAO,SAAS;AAChB,yBAAa,QAAQ;AAAA,UACvB;AAEA,gBAAM,YAAY,KAAK,IAAI,KAAK,UAAU,QAAQ,EAAE,OAAO,KAAK,MAAM,CAAC;AACvE,cAAI;AACF,mBAAO,CAAC,OAAO,SAAS;AACtB,oBAAM,EAAE,OAAO,OAAO,KAAK,IAAI,MAAM,UAAU,KAAK;AACpD,kBAAI,QAAQ,CAAC,MAAO;AACpB,kBAAI,OAAO,QAAS;AAEpB,kBAAI,MAAM,SAAS,aAAa,MAAM,SAAS;AAE7C,oBAAI,cAAc;AAChB,mCAAiB,YAAY,IAAI;AACjC,iCAAe;AACf,kBAAAA,KAAI,KAAK,uBAAuB,iBAAiB,YAAY,QAAQ,CAAC,CAAC,IAAI;AAAA,gBAC7E;AAGA,oBAAI,aAAc,iBAAgB;AAClC,gCAAgB,MAAM,QAAQ;AAE9B,uBAAO,KAAK,MAAM,OAAO;AAGzB,oBAAI,mBAAmB,KAAK,MAAM,QAAQ,IAAI,GAAG;AAC/C,gCAAc;AAAA,gBAChB;AAAA,cACF,WAAW,MAAM,SAAS,WAAW,MAAM,OAAO;AAEhD,oBAAI,cAAc;AAChB,mCAAiB,YAAY,IAAI;AACjC,iCAAe;AACf,kBAAAA,KAAI,KAAK,qBAAqB,iBAAiB,YAAY,QAAQ,CAAC,CAAC,IAAI;AAAA,gBAC3E;AAEA,gCAAgB,MAAM;AAEtB,sBAAM,YAAY,KAAK,SAAS,KAAK,MAAM,KAAK;AAChD,2BAAW,YAAY,WAAW;AAChC,+BAAa,QAAQ;AAAA,gBACvB;AAAA,cACF,WAAW,MAAM,SAAS,eAAe,MAAM,UAAU;AACvD,gBAAAA,KAAI,KAAK,cAAc,MAAM,SAAS,IAAI,IAAI,MAAM,SAAS,SAAS,GAAG;AACzE,kCAAkB;AAClB,qBAAK,KAAK,YAAY,MAAM,QAAQ;AAAA,cACtC;AAAA,YACF;AAAA,UACF,UAAE;AACA,kBAAM,UAAU,OAAO,MAAS;AAAA,UAClC;AAGA,cAAI,CAAC,OAAO,SAAS;AACnB,0BAAc;AACd,kBAAM,YAAY,KAAK,SAAS,MAAM;AACtC,gBAAI,WAAW;AACb,2BAAa,SAAS;AAAA,YACxB;AAEA,gBAAI,aAAa,KAAK,KAAK,iBAAiB;AAC1C;AAAA,YACF;AAEA,YAAAA,KAAI,KAAK,mCAAmC,UAAU,CAAC,IAAI,kBAAkB,CAAC,GAAG;AAAA,UACnF;AAAA,QACF;AAEA,uBAAe;AACf,aAAK;AAAA,MACP;AAMA,YAAM,WAAW,YAAY;AAC3B,aAAK,YAAY,cAAc;AAE/B,cAAM,QAA2C,EAAE,YAAY,KAAK;AACpE,YAAI;AACF,iBAAO,MAAM;AACX,gBAAI,OAAO,QAAS;AAEpB,gBAAI;AACJ,gBAAI;AAEJ,gBAAI,MAAM,YAAY;AACpB,yBAAW,MAAM,WAAW;AAC5B,+BAAiB,MAAM,WAAW,SAAS;AAC3C,oBAAM,aAAa;AAAA,YACrB,WAAW,cAAc,SAAS,GAAG;AACnC,yBAAW,cAAc,MAAM;AAC/B,kBAAI,CAAC,KAAK,KAAK,QAAQ,GAAG;AACxB,gBAAAA,KAAI,MAAM,gCAAgC,QAAQ,GAAG;AACrD;AAAA,cACF;AACA,+BAAiB;AAAA,YACnB,WAAW,cAAc;AACvB;AAAA,YACF,OAAO;AACL,oBAAM,IAAI,QAAc,CAAC,YAAY;AACnC,+BAAe;AAAA,cACjB,CAAC;AACD,6BAAe;AACf;AAAA,YACF;AAGA,kBAAM,cAAc,MAAM;AACxB,kBAAI,MAAM,cAAc,CAAC,KAAK,IAAK;AACnC,kBAAI,cAAc,SAAS,GAAG;AAC5B,sBAAM,OAAO,cAAc,MAAM;AACjC,oBAAI,KAAK,KAAK,IAAI,GAAG;AACnB,wBAAM,aAAa,EAAE,UAAU,MAAM,UAAU,YAAY,KAAK,KAAK,MAAM,MAAM,EAAE;AAAA,gBACrF;AAAA,cACF;AAAA,YACF;AACA,wBAAY;AAEZ,gBAAI;AACF,oBAAM,KAAK,kBAAkB,UAAU,QAAQ,CAAC,MAAM;AACpD,oBAAI,CAAC,mBAAmB;AACtB,sCAAoB;AACpB,uBAAK,cAAc,UAAU;AAAA,gBAC/B;AACA,qBAAK,KAAK,YAAY,KAAK,UAAU,QAAQ,GAAG,QAAQ;AACxD,4BAAY;AAAA,cACd,GAAG,cAAc;AAAA,YACnB,SAAS,QAAiB;AAGxB,kBAAI,kBAAkB,SAAS,OAAO,SAAS,aAAc,OAAM;AACnE,cAAAA,KAAI,KAAK,uCAAuC,SAAS,MAAM,GAAG,EAAE,CAAC,KAAK,MAAM;AAAA,YAClF;AAAA,UACF;AAAA,QACF,UAAE;AACA,cAAI,CAAC,OAAO,SAAS;AACnB,kBAAM,KAAK,YAAY,aAAa,EAAE;AAAA,UACxC;AACA,eAAK,YAAY,YAAY;AAAA,QAC/B;AAAA,MACF;AAEA,YAAM,QAAQ,IAAI,CAAC,SAAS,GAAG,SAAS,CAAC,CAAC;AAO1C,YAAM,SAAS,iBAAiB,iBAAiB,aAAa;AAC9D,YAAM,QAAQ,iBAAiB,iBAAiB,aAAa;AAC7D,YAAM,QAAQ,qBAAqB,iBAAiB,oBAAoB,iBAAiB;AACzF,YAAM,YAAY,cAAc,QAAQ;AAExC,MAAAA,KAAI;AAAA,QACF,YAAY,KAAK,MAAM,GAAG,EAAE,CAAC,UACtB,YAAY,QAAQ,CAAC,CAAC,UACtB,MAAM,QAAQ,CAAC,CAAC,YAAY,OAAO,QAAQ,CAAC,CAAC,WAC7C,MAAM,QAAQ,CAAC,CAAC,cACZ,UAAU,QAAQ,CAAC,CAAC;AAAA,MACjC;AAEA,UAAI,aAAa,KAAK,GAAG;AACvB,aAAK,QAAQ,aAAa,aAAa,KAAK,CAAC;AAC7C,aAAK,QAAQ,UAAU,aAAa,aAAa,KAAK,GAAG,IAAI;AAC7D,aAAK,KAAK,YAAY,KAAK,UAAU,aAAa,KAAK,CAAC,CAAC;AAAA,MAC3D;AAIA,YAAMC,OAAM,cAAc;AAC1B,WAAK,cAAc,MAAM;AAAA,IAC3B,SAAS,KAAc;AACrB,UAAI,eAAe,SAAS,IAAI,SAAS,cAAc;AACrD,QAAAD,KAAI,MAAM,oCAAoC;AAAA,MAChD,OAAO;AACL,QAAAA,KAAI,MAAM,0BAA0B,GAAG;AACvC,aAAK,KAAK,SAAS,eAAe,QAAQ,MAAM,IAAI,MAAM,OAAO,GAAG,CAAC,CAAC;AAAA,MACxE;AAAA,IACF,UAAE;AACA,WAAK,cAAc;AACnB,WAAK,QAAQ,MAAM;AAEnB,UAAI,KAAK,aAAa;AACpB,cAAM,EAAE,SAAS,aAAa,MAAM,SAAS,IAAI,KAAK;AACtD,aAAK,cAAc;AACnB,QAAAA,KAAI,KAAK,gCAAgC,WAAW,MAAM,QAAQ,EAAE;AACpE,aAAK,YAAY,aAAa,QAAQ;AAAA,MACxC;AAAA,IACF;AAAA,EACF;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA,EAOA,MAAM,IAAI,MAA6B;AACrC,QAAI,KAAK,aAAa;AACpB,MAAAA,KAAI,KAAK,+CAA0C;AACnD;AAAA,IACF;AAEA,SAAK,cAAc;AACnB,UAAM,QAAQ,IAAI,CAAC,KAAK,mBAAmB,KAAK,kBAAkB,CAAC;AACnE,IAAAA,KAAI,KAAK,WAAW,KAAK,MAAM,GAAG,EAAE,CAAC,GAAG;AAExC,QAAI;AACF,YAAM,SAAS,KAAK,QAAQ,WAAW;AACvC,WAAK,YAAY,cAAc;AAC/B,WAAK,cAAc,UAAU;AAE7B,YAAM,KAAK,kBAAkB,MAAM,QAAQ,MAAM;AAC/C,aAAK,cAAc,UAAU;AAC7B,aAAK,KAAK,YAAY,KAAK,UAAU,IAAI,GAAG,IAAI;AAAA,MAClD,CAAC;AAED,UAAI,CAAC,OAAO,SAAS;AACnB,cAAM,KAAK,YAAY,aAAa,EAAE;AACtC,aAAK,QAAQ,aAAa,IAAI;AAC9B,aAAK,QAAQ,UAAU,aAAa,MAAM,IAAI;AAC9C,aAAK,KAAK,YAAY,KAAK,UAAU,IAAI,CAAC;AAAA,MAC5C;AAGA,YAAMC,OAAM,cAAc;AAC1B,WAAK,cAAc,MAAM;AAAA,IAC3B,SAAS,KAAc;AACrB,UAAI,eAAe,SAAS,IAAI,SAAS,cAAc;AACrD,QAAAD,KAAI,MAAM,0BAA0B;AAAA,MACtC,OAAO;AACL,QAAAA,KAAI,MAAM,mBAAmB,GAAG;AAChC,aAAK,KAAK,SAAS,eAAe,QAAQ,MAAM,IAAI,MAAM,OAAO,GAAG,CAAC,CAAC;AAAA,MACxE;AAAA,IACF,UAAE;AACA,WAAK,cAAc;AACnB,WAAK,YAAY,YAAY;AAC7B,WAAK,QAAQ,MAAM;AAEnB,UAAI,KAAK,aAAa;AACpB,cAAM,EAAE,SAAS,aAAa,MAAM,SAAS,IAAI,KAAK;AACtD,aAAK,cAAc;AACnB,QAAAA,KAAI,KAAK,gCAAgC,WAAW,MAAM,QAAQ,EAAE;AACpE,aAAK,YAAY,aAAa,QAAQ;AAAA,MACxC;AAAA,IACF;AAAA,EACF;AAAA,EAEA,MAAc,kBACZ,MACA,QACA,cACA,gBACe;AACf,QAAI,CAAC,KAAK,OAAO,OAAO,SAAS;AAC/B,MAAAA,KAAI,KAAK,iBAAiB,IAAI,EAAE;AAChC;AAAA,IACF;AAEA,QAAI;AACF,YAAM,WAAW,YAAY,IAAI;AACjC,UAAI,aAAa;AACjB,UAAI,gBAAgB;AAEpB,YAAM,YAAY,kBAAkB,KAAK,IAAI,WAAW,MAAM,MAAM;AACpE,YAAM,iBAAiB,mBAAmB;AACxC,yBAAiB,SAAS,WAAW;AACnC;AACA,cAAI,YAAY;AACd,yBAAa;AACb,kBAAM,MAAM,YAAY,IAAI;AAC5B,YAAAA,KAAI,KAAK,qBAAqB,MAAM,UAAU,QAAQ,CAAC,CAAC,WAAW,KAAK,MAAM,GAAG,EAAE,CAAC,GAAG;AACvF,yBAAa,GAAG;AAAA,UAClB;AACA,gBAAM;AAAA,QACR;AAAA,MACF;AAEA,YAAM,KAAK,YAAY,YAAY,eAAe,GAAG,MAAM;AAC3D,MAAAA,KAAI,KAAK,4BAA4B,YAAY,IAAI,IAAI,UAAU,QAAQ,CAAC,CAAC,OAAO,aAAa,gBAAgB,KAAK,MAAM,GAAG,EAAE,CAAC,GAAG;AAAA,IACvI,SAAS,KAAc;AACrB,UAAI,eAAe,SAAS,IAAI,SAAS,aAAc;AACvD,YAAM;AAAA,IACR;AAAA,EACF;AACF;;;AJlqBA,IAAMG,OAAM,aAAa,YAAY;AAE9B,IAAM,aAAN,cAAyBC,cAAa;AAAA,EAC1B;AAAA,EACT,aAAoC;AAAA,EACpC,WAA4B;AAAA,EAC5B,cAAc,oBAAI,IAAwB;AAAA,EAC1C,cAAkC;AAAA,EAClC,SAA4D;AAAA,EAC5D,WAAW;AAAA,EAEnB,YAAY,QAAqB;AAC/B,UAAM;AACN,SAAK,SAAS;AAAA,EAChB;AAAA,EAEA,IAAI,UAAmB;AACrB,WAAO,KAAK;AAAA,EACd;AAAA,EAEA,IAAI,OAAO;AAAE,WAAO,KAAK,YAAY,QAAQ;AAAA,EAAM;AAAA;AAAA,EAGnD,gBAAgB,KAAmB;AACjC,SAAK,WAAW;AAChB,QAAI,KAAK,aAAa;AACpB,WAAK,YAAY,UAAU;AAAA,IAC7B;AAAA,EACF;AAAA,EACQ,WAA0B;AAAA;AAAA;AAAA;AAAA;AAAA,EAMlC,MAAM,IAAI,MAA6B;AACrC,QAAI,CAAC,KAAK,UAAU;AAClB,YAAM,IAAI,MAAM,6CAAwC;AAAA,IAC1D;AACA,UAAM,KAAK,SAAS,IAAI,IAAI;AAAA,EAC9B;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA,EAOA,eAAe,UAAkB,SAA4C;AAC3E,SAAK,UAAU,eAAe,UAAU,OAAO;AAAA,EACjD;AAAA;AAAA,EAGA,MAAM,MAAM,SAA2C;AACrD,QAAI,KAAK,UAAU;AACjB,YAAM,IAAI,MAAM,0BAA0B;AAAA,IAC5C;AAEA,IAAAD,KAAI,KAAK,4BAA4B,QAAQ,IAAI,MAAM;AAGvD,QAAI,KAAK,OAAO,QAAQ,SAAS;AAC/B,YAAM,EAAE,WAAW,IAAI,MAAM,OAAO,4BAAuB;AAC3D,WAAK,SAAS,IAAI,WAAW;AAAA,QAC3B,QAAQ,KAAK,OAAO,OAAO,UAAU;AAAA,QACrC,MAAM,KAAK,OAAO,OAAO,SAAS,QAAQ;AAAA,MAC5C,CAAC;AACD,YAAM,KAAK,OAAO,KAAK;AACvB,WAAK,OAAO,aAAa;AACzB,MAAAA,KAAI,KAAK,oBAAoB;AAAA,IAC/B;AAGA,SAAK,aAAa,IAAI,eAAe;AACrC,UAAM,KAAK,WAAW,QAAQ;AAAA,MAC5B,MAAM,QAAQ;AAAA,MACd,QAAQ,QAAQ;AAAA,MAChB,WAAW,QAAQ;AAAA,MACnB,OAAO,QAAQ;AAAA,MACf,OAAO,QAAQ;AAAA,MACf,UAAU,QAAQ,YAAY;AAAA,MAC9B,MAAM,QAAQ,QAAQ,QAAQ,YAAY;AAAA,IAC5C,CAAC;AAGD,UAAM,SAAS,MAAM,KAAK,WAAW,kBAAkB;AACvD,SAAK,cAAc,IAAI,YAAY,MAAM;AACzC,QAAI,KAAK,SAAU,MAAK,YAAY,UAAU,KAAK;AACnD,SAAK,YAAY,aAAa;AAG9B,SAAK,WAAW,IAAI,SAAS;AAAA,MAC3B,KAAK,KAAK,OAAO;AAAA,MACjB,KAAK,KAAK,OAAO;AAAA,MACjB,KAAK,KAAK,OAAO;AAAA,MACjB,cAAc,KAAK,OAAO;AAAA,MAC1B,aAAa,KAAK;AAAA,MAClB,aAAa,KAAK,OAAO;AAAA,MACzB,WAAW,KAAK,OAAO;AAAA,MACvB,cAAc,KAAK,OAAO;AAAA,MAC1B,QAAQ,KAAK,UAAU;AAAA,MACvB,kBAAkB,KAAK,OAAO;AAAA,MAC9B,OAAO,KAAK,OAAO;AAAA,IACrB,CAAC;AAGD,SAAK,SAAS,GAAG,iBAAiB,CAAC,WAAW,KAAK,KAAK,iBAAiB,MAAM,CAAC;AAChF,SAAK,SAAS,GAAG,YAAY,CAAC,MAAM,QAAQ,KAAK,KAAK,YAAY,MAAM,GAAG,CAAC;AAC5E,SAAK,SAAS,GAAG,YAAY,CAAC,SAAS,KAAK,KAAK,YAAY,IAAI,CAAC;AAClE,SAAK,SAAS,GAAG,cAAc,CAAC,UAAU,KAAK,KAAK,cAAc,KAAK,CAAC;AACxE,SAAK,SAAS,GAAG,YAAY,CAAC,OAAO,KAAK,KAAK,YAAY,EAAE,CAAC;AAC9D,SAAK,SAAS,GAAG,SAAS,CAAC,UAAU,KAAK,KAAK,SAAS,KAAK,CAAC;AAG9D,eAAW,eAAe,KAAK,WAAW,KAAK,mBAAmB,OAAO,GAAG;AAC1E,iBAAW,CAAC,EAAE,GAAG,KAAK,YAAY,mBAAmB;AACnD,YAAI,IAAI,OAAO;AACb,eAAK,sBAAsB,IAAI,OAA2B,KAA+B,WAAW;AAAA,QACtG;AAAA,MACF;AAAA,IACF;AAGA,SAAK,WAAW,KAAK,GAAG,mBAAmB,CAAC,OAAO,KAAK,gBAAgB;AACtE,WAAK,sBAAsB,OAAO,KAAK,WAAW;AAAA,IACpD,CAAC;AAED,SAAK,WAAW,KAAK,GAAG,qBAAqB,CAAC,OAAO,MAAM,gBAAgB;AACzE,WAAK,wBAAwB,OAAO,WAAW;AAAA,IACjD,CAAC;AAED,SAAK,WAAW,KAAK,GAAG,2BAA2B,CAAC,gBAAgB;AAClE,WAAK,8BAA8B,WAAW;AAAA,IAChD,CAAC;AAED,SAAK,WAAW,KAAK,GAAG,gBAAgB,CAAC,WAAW;AAClD,MAAAA,KAAI,KAAK,sBAAsB,MAAM,EAAE;AACvC,WAAK,KAAK,gBAAgB,MAAM;AAAA,IAClC,CAAC;AAGD,QAAI,KAAK,OAAO,eAAe;AAC7B,WAAK,iBAAiB,KAAK,OAAO,aAAa;AAAA,IACjD;AAEA,SAAK,WAAW;AAChB,SAAK,KAAK,WAAW;AACrB,IAAAA,KAAI,KAAK,6BAA6B;AAAA,EACxC;AAAA;AAAA,EAGA,MAAM,OAAsB;AAC1B,QAAI,CAAC,KAAK,SAAU;AAEpB,IAAAA,KAAI,KAAK,mBAAmB;AAC5B,SAAK,WAAW;AAEhB,QAAI,KAAK,UAAU;AACjB,YAAM,KAAK,SAAS,KAAK;AACzB,WAAK,WAAW;AAAA,IAClB;AAGA,QAAI,KAAK,QAAQ;AACf,UAAI;AACF,cAAM,KAAK,OAAO,WAAW,KAAK,OAAO,GAAG;AAC5C,cAAM,KAAK,OAAO,MAAM;AAAA,MAC1B,SAAS,KAAK;AACZ,QAAAA,KAAI,MAAM,yBAAyB,GAAG;AAAA,MACxC;AACA,WAAK,SAAS;AAAA,IAChB;AAEA,eAAW,CAAC,EAAE,KAAK,KAAK,KAAK,aAAa;AACxC,YAAM,MAAM;AAAA,IACd;AACA,SAAK,YAAY,MAAM;AAEvB,QAAI,KAAK,aAAa;AACpB,WAAK,YAAY,KAAK;AACtB,WAAK,cAAc;AAAA,IACrB;AAEA,QAAI,KAAK,YAAY;AACnB,YAAM,KAAK,WAAW,WAAW;AACjC,WAAK,aAAa;AAAA,IACpB;AAEA,SAAK,KAAK,gBAAgB,eAAe;AACzC,IAAAA,KAAI,KAAK,eAAe;AAAA,EAC1B;AAAA,EAEQ,iBAAiB,SAAmC;AAC1D,QAAI,CAAC,KAAK,WAAY;AAEtB,SAAK,WAAW,KAAK,GAAG,gBAAgB,CAAC,SAAqB,aAAiC,OAAiB,UAAmB;AACjI,YAAM,WAAW,aAAa,YAAY;AAC1C,cAAQ,SAAS,UAAU,KAAK;AAAA,IAClC,CAAC;AAED,IAAAA,KAAI,KAAK,iCAAiC;AAAA,EAC5C;AAAA,EAEQ,sBACN,OACA,cACA,aACM;AACN,UAAM,WAAW,YAAY;AAC7B,IAAAA,KAAI,KAAK,0BAA0B,QAAQ,UAAU,MAAM,GAAG,GAAG;AAGjE,SAAK,QAAQ,eAAe,QAAQ;AAGpC,UAAM,WAAW,KAAK,YAAY,IAAI,QAAQ;AAC9C,QAAI,UAAU;AACZ,MAAAA,KAAI,KAAK,+BAA+B,QAAQ,qBAAqB;AACrE,eAAS,MAAM;AAAA,IACjB;AAEA,UAAM,aAAa,IAAI,WAAW,OAAO,QAAQ;AACjD,SAAK,YAAY,IAAI,UAAU,UAAU;AAEzC,UAAM,YAAY,KAAK,SAAU,eAAe,QAAQ;AAExD,SAAK,eAAe,YAAY,WAAW,QAAQ;AAAA,EACrD;AAAA,EAEQ,wBACN,QACA,aACM;AACN,UAAM,WAAW,YAAY;AAC7B,IAAAA,KAAI,KAAK,4BAA4B,QAAQ,GAAG;AAEhD,UAAM,QAAQ,KAAK,YAAY,IAAI,QAAQ;AAC3C,QAAI,OAAO;AACT,YAAM,MAAM;AACZ,WAAK,YAAY,OAAO,QAAQ;AAAA,IAClC;AAAA,EACF;AAAA,EAEQ,8BAA8B,aAAsC;AAC1E,UAAM,WAAW,YAAY;AAC7B,IAAAA,KAAI,KAAK,8BAA8B,QAAQ,GAAG;AAElD,UAAM,QAAQ,KAAK,YAAY,IAAI,QAAQ;AAC3C,QAAI,OAAO;AACT,YAAM,MAAM;AACZ,WAAK,YAAY,OAAO,QAAQ;AAAA,IAClC;AAEA,SAAK,UAAU,kBAAkB,QAAQ;AAGzC,QAAI,KAAK,YAAY,KAAK,YAAY,KAAK,mBAAmB,SAAS,GAAG;AACxE,MAAAA,KAAI,KAAK,6CAAwC;AACjD,WAAK,KAAK;AAAA,IACZ;AAAA,EACF;AAAA,EAEA,MAAc,eACZ,OACA,WACA,UACe;AACf,QAAI;AACF,uBAAiB,UAAU,MAAM,OAAO,GAAG;AACzC,YAAI,CAAC,KAAK,SAAU;AACpB,kBAAU,UAAU,MAAM;AAAA,MAC5B;AAAA,IACF,SAAS,KAAK;AACZ,UAAI,KAAK,UAAU;AACjB,QAAAA,KAAI,MAAM,yBAAyB,QAAQ,MAAM,GAAG;AAAA,MACtD;AAAA,IACF;AAAA,EACF;AACF;","names":["EventEmitter","log","log","log","log","log","log","sleep","text","log","EventEmitter"]}
1
+ {"version":3,"sources":["../src/core/voice-agent.ts","../src/room/room-connection.ts","../src/room/audio-input.ts","../src/room/audio-output.ts","../src/core/pipeline.ts","../src/core/context-manager.ts","../src/core/sentence-splitter.ts","../src/core/turn-detector.ts","../src/core/barge-in.ts"],"sourcesContent":["/**\n * VoiceAgent — top-level orchestrator for AI voice agents in dTelecom rooms.\n *\n * Wires together:\n * - RoomConnection (join room, publish audio track)\n * - Pipeline (STT -> LLM -> TTS)\n * - AudioInput (per-participant audio streams)\n * - AudioOutput (agent's published audio)\n * - RoomMemory (optional persistent memory)\n */\n\nimport { EventEmitter } from 'events';\nimport type { RemoteAudioTrack, RemoteTrackPublication, RemoteParticipant } from '@dtelecom/server-sdk-node';\nimport { RoomConnection } from '../room/room-connection';\nimport { AudioInput } from '../room/audio-input';\nimport { AudioOutput } from '../room/audio-output';\nimport { Pipeline } from './pipeline';\nimport type { AgentConfig, AgentStartOptions, DataMessageHandler } from './types';\nimport { createLogger } from '../utils/logger';\n\nconst log = createLogger('VoiceAgent');\n\nexport class VoiceAgent extends EventEmitter {\n private readonly config: AgentConfig;\n private connection: RoomConnection | null = null;\n private pipeline: Pipeline | null = null;\n private audioInputs = new Map<string, AudioInput>();\n private audioOutput: AudioOutput | null = null;\n private memory: import('../memory/room-memory').RoomMemory | null = null;\n private _running = false;\n\n constructor(config: AgentConfig) {\n super();\n this.config = config;\n }\n\n get running(): boolean {\n return this._running;\n }\n\n get room() { return this.connection?.room ?? null; }\n\n /** Enable saving raw TTS audio as WAV files to `dir` for debugging. */\n enableAudioDump(dir: string): void {\n this._dumpDir = dir;\n if (this.audioOutput) {\n this.audioOutput.dumpDir = dir;\n }\n }\n private _dumpDir: string | null = null;\n\n /**\n * Speak text directly via TTS, bypassing the LLM.\n * Use for greetings or announcements. Supports barge-in.\n */\n async say(text: string): Promise<void> {\n if (!this.pipeline) {\n throw new Error('Agent not started — call start() first');\n }\n await this.pipeline.say(text);\n }\n\n /**\n * Switch STT language on all active streams.\n * Use for bilingual lessons — e.g. switch to 'es' with forceWhisper when\n * expecting Spanish, back to 'auto' for Parakeet auto-detect otherwise.\n */\n setSTTLanguage(language: string, options?: { forceWhisper?: boolean }): void {\n this.pipeline?.setSTTLanguage(language, options);\n }\n\n /** Start the agent — connect to room and begin listening. */\n async start(options: AgentStartOptions): Promise<void> {\n if (this._running) {\n throw new Error('Agent is already running');\n }\n\n log.info(`Starting agent for room \"${options.room}\"...`);\n\n // 1. Initialize memory (if enabled)\n if (this.config.memory?.enabled) {\n const { RoomMemory } = await import('../memory/room-memory');\n this.memory = new RoomMemory({\n dbPath: this.config.memory.dbPath ?? './data/memory.db',\n room: this.config.memory.scope ?? options.room,\n });\n await this.memory.init();\n this.memory.startSession();\n log.info('Memory initialized');\n }\n\n // 2. Connect to room\n this.connection = new RoomConnection();\n await this.connection.connect({\n room: options.room,\n apiKey: options.apiKey,\n apiSecret: options.apiSecret,\n token: options.token,\n wsUrl: options.wsUrl,\n identity: options.identity ?? 'agent',\n name: options.name ?? options.identity ?? 'AI Agent',\n });\n\n // 3. Publish audio track + start sending silence to keep it active\n const source = await this.connection.publishAudioTrack();\n this.audioOutput = new AudioOutput(source);\n if (this._dumpDir) this.audioOutput.dumpDir = this._dumpDir;\n this.audioOutput.startSilence();\n\n // 4. Create pipeline (warmup is handled internally by Pipeline)\n this.pipeline = new Pipeline({\n stt: this.config.stt,\n llm: this.config.llm,\n tts: this.config.tts,\n instructions: this.config.instructions,\n audioOutput: this.audioOutput,\n respondMode: this.config.respondMode,\n agentName: this.config.agentName,\n nameVariants: this.config.nameVariants,\n memory: this.memory ?? undefined,\n maxContextTokens: this.config.maxContextTokens,\n tools: this.config.tools,\n });\n\n // Forward pipeline events\n this.pipeline.on('transcription', (result) => this.emit('transcription', result));\n this.pipeline.on('sentence', (text, raw) => this.emit('sentence', text, raw));\n this.pipeline.on('response', (text) => this.emit('response', text));\n this.pipeline.on('agentState', (state) => this.emit('agentState', state));\n this.pipeline.on('toolCall', (tc) => this.emit('toolCall', tc));\n this.pipeline.on('error', (error) => this.emit('error', error));\n\n // 5. Subscribe to existing remote participants\n for (const participant of this.connection.room.remoteParticipants.values()) {\n for (const [, pub] of participant.trackPublications) {\n if (pub.track) {\n this.handleTrackSubscribed(pub.track as RemoteAudioTrack, pub as RemoteTrackPublication, participant);\n }\n }\n }\n\n // 6. Listen for new tracks\n this.connection.room.on('trackSubscribed', (track, pub, participant) => {\n this.handleTrackSubscribed(track, pub, participant);\n });\n\n this.connection.room.on('trackUnsubscribed', (track, _pub, participant) => {\n this.handleTrackUnsubscribed(track, participant);\n });\n\n this.connection.room.on('participantDisconnected', (participant) => {\n this.handleParticipantDisconnected(participant);\n });\n\n this.connection.room.on('disconnected', (reason) => {\n log.info(`Room disconnected: ${reason}`);\n this.emit('disconnected', reason);\n });\n\n // 7. Data channel support\n if (this.config.onDataMessage) {\n this.setupDataChannel(this.config.onDataMessage);\n }\n\n this._running = true;\n this.emit('connected');\n log.info('Agent started and listening');\n }\n\n /** Stop the agent — disconnect and clean up. */\n async stop(): Promise<void> {\n if (!this._running) return;\n\n log.info('Stopping agent...');\n this._running = false;\n\n if (this.pipeline) {\n await this.pipeline.stop();\n this.pipeline = null;\n }\n\n // End memory session (generates summary)\n if (this.memory) {\n try {\n await this.memory.endSession(this.config.llm);\n await this.memory.close();\n } catch (err) {\n log.error('Error closing memory:', err);\n }\n this.memory = null;\n }\n\n for (const [, input] of this.audioInputs) {\n input.close();\n }\n this.audioInputs.clear();\n\n if (this.audioOutput) {\n this.audioOutput.stop();\n this.audioOutput = null;\n }\n\n if (this.connection) {\n await this.connection.disconnect();\n this.connection = null;\n }\n\n this.emit('disconnected', 'agent_stopped');\n log.info('Agent stopped');\n }\n\n private setupDataChannel(handler: DataMessageHandler): void {\n if (!this.connection) return;\n\n this.connection.room.on('dataReceived', (payload: Uint8Array, participant?: RemoteParticipant, _kind?: unknown, topic?: string) => {\n const identity = participant?.identity ?? 'unknown';\n handler(payload, identity, topic);\n });\n\n log.info('Data channel handler registered');\n }\n\n private handleTrackSubscribed(\n track: RemoteAudioTrack,\n _publication: RemoteTrackPublication,\n participant: RemoteParticipant,\n ): void {\n const identity = participant.identity;\n log.info(`Track subscribed from \"${identity}\" (sid=${track.sid})`);\n\n // Track participant in memory\n this.memory?.addParticipant(identity);\n\n // Close existing AudioInput if this is a re-subscription\n const existing = this.audioInputs.get(identity);\n if (existing) {\n log.info(`Closing old AudioInput for \"${identity}\" (re-subscription)`);\n existing.close();\n }\n\n const audioInput = new AudioInput(track, identity);\n this.audioInputs.set(identity, audioInput);\n\n const sttStream = this.pipeline!.addParticipant(identity);\n\n this.pipeAudioToSTT(audioInput, sttStream, identity);\n }\n\n private handleTrackUnsubscribed(\n _track: RemoteAudioTrack,\n participant: RemoteParticipant,\n ): void {\n const identity = participant.identity;\n log.info(`Track unsubscribed from \"${identity}\"`);\n\n const input = this.audioInputs.get(identity);\n if (input) {\n input.close();\n this.audioInputs.delete(identity);\n }\n }\n\n private handleParticipantDisconnected(participant: RemoteParticipant): void {\n const identity = participant.identity;\n log.info(`Participant disconnected: \"${identity}\"`);\n\n const input = this.audioInputs.get(identity);\n if (input) {\n input.close();\n this.audioInputs.delete(identity);\n }\n\n this.pipeline?.removeParticipant(identity);\n\n // If no remote participants remain, stop the agent (process cleanup)\n if (this._running && this.connection?.room.remoteParticipants.size === 0) {\n log.info('All participants left — stopping agent');\n this.stop();\n }\n }\n\n private async pipeAudioToSTT(\n input: AudioInput,\n sttStream: { sendAudio(pcm16: Buffer): void },\n identity: string,\n ): Promise<void> {\n try {\n for await (const buffer of input.frames()) {\n if (!this._running) break;\n sttStream.sendAudio(buffer);\n }\n } catch (err) {\n if (this._running) {\n log.error(`Audio pipe error for \"${identity}\":`, err);\n }\n }\n }\n}\n","import { Room, LocalAudioTrack, AudioSource, TrackSource } from '@dtelecom/server-sdk-node';\nimport { createLogger } from '../utils/logger';\n\nconst log = createLogger('RoomConnection');\n\nexport interface RoomConnectionOptions {\n room: string;\n identity?: string;\n name?: string;\n\n // Mode 1: Generate token from credentials (existing)\n apiKey?: string;\n apiSecret?: string;\n\n // Mode 2: Use pre-signed token from x402 gateway\n token?: string;\n wsUrl?: string;\n}\n\nexport class RoomConnection {\n readonly room: Room;\n private audioSource: AudioSource | null = null;\n private localTrack: LocalAudioTrack | null = null;\n private _connected = false;\n\n constructor() {\n this.room = new Room();\n }\n\n get connected(): boolean {\n return this._connected;\n }\n\n /**\n * Connect to a dTelecom room.\n *\n * 1. Create an Ed25519 JWT via AccessToken\n * 2. Discover nearest SFU via getWsUrl()\n * 3. Connect Room via WebRTC\n * 4. Publish an audio track for the agent to speak through\n */\n async connect(options: RoomConnectionOptions): Promise<void> {\n const { room: roomName, identity = 'agent', name } = options;\n\n log.info(`Connecting to room \"${roomName}\" as \"${identity}\"...`);\n\n let jwt: string;\n let wsUrl: string;\n\n if (options.token && options.wsUrl) {\n // Mode 2: Use pre-signed token from x402 gateway\n jwt = options.token;\n wsUrl = options.wsUrl;\n } else if (options.apiKey && options.apiSecret) {\n // Mode 1: Generate token from credentials\n const { AccessToken } = await import('@dtelecom/server-sdk-js');\n\n const token = new AccessToken(options.apiKey, options.apiSecret, {\n identity,\n name: name ?? identity,\n });\n token.addGrant({\n roomJoin: true,\n room: roomName,\n canPublish: true,\n canSubscribe: true,\n canPublishData: true,\n });\n\n wsUrl = await token.getWsUrl();\n jwt = token.toJwt();\n } else {\n throw new Error('Either (apiKey + apiSecret) or (token + wsUrl) must be provided');\n }\n\n log.info(`SFU URL: ${wsUrl}`);\n\n // Connect\n await this.room.connect(wsUrl, jwt, { autoSubscribe: true });\n this._connected = true;\n\n log.info('Connected successfully');\n }\n\n /**\n * Publish an audio track so the agent can speak.\n * Returns the AudioSource to feed PCM16 audio into.\n */\n async publishAudioTrack(): Promise<AudioSource> {\n if (this.audioSource) return this.audioSource;\n\n // 48kHz mono — matches Opus/WebRTC native rate, no resampling needed\n this.audioSource = new AudioSource(48000, 1);\n this.localTrack = LocalAudioTrack.createAudioTrack('agent-voice', this.audioSource);\n\n await this.room.localParticipant.publishTrack(this.localTrack, {\n name: 'agent-voice',\n source: TrackSource.MICROPHONE,\n });\n\n log.info('Audio track published');\n return this.audioSource;\n }\n\n /** Disconnect from the room and clean up resources. */\n async disconnect(): Promise<void> {\n if (!this._connected) return;\n\n if (this.localTrack) {\n await this.room.localParticipant.unpublishTrack(this.localTrack);\n this.localTrack = null;\n }\n\n if (this.audioSource) {\n this.audioSource.destroy();\n this.audioSource = null;\n }\n\n await this.room.disconnect();\n this._connected = false;\n\n log.info('Disconnected from room');\n }\n}\n","import { RemoteAudioTrack, AudioStream, AudioFrame } from '@dtelecom/server-sdk-node';\nimport { createLogger } from '../utils/logger';\n\nconst log = createLogger('AudioInput');\n\nexport class AudioInput {\n readonly participantIdentity: string;\n private stream: AudioStream;\n private _closed = false;\n private frameCount = 0;\n\n constructor(track: RemoteAudioTrack, participantIdentity: string) {\n this.participantIdentity = participantIdentity;\n // 16kHz mono — standard for STT\n this.stream = track.createStream(16000, 1);\n log.info(`AudioInput created for \"${participantIdentity}\" (trackSid=${track.sid})`);\n }\n\n get closed(): boolean {\n return this._closed;\n }\n\n /**\n * Async iterate over PCM16 buffers from this participant.\n * Each yielded Buffer is 16kHz mono PCM16 LE.\n */\n async *frames(): AsyncGenerator<Buffer> {\n for await (const frame of this.stream) {\n if (this._closed) break;\n this.frameCount++;\n if (this.frameCount === 1 || this.frameCount % 500 === 0) {\n log.info(`[${this.participantIdentity}] frame #${this.frameCount}`);\n }\n yield frame.toBuffer();\n }\n log.info(`[${this.participantIdentity}] frame iterator ended (total: ${this.frameCount})`);\n }\n\n /** Async iterate over AudioFrame objects. */\n async *audioFrames(): AsyncGenerator<AudioFrame> {\n for await (const frame of this.stream) {\n if (this._closed) break;\n yield frame;\n }\n }\n\n close(): void {\n if (this._closed) return;\n this._closed = true;\n this.stream.close();\n log.debug(`AudioInput closed for participant \"${this.participantIdentity}\"`);\n }\n}\n","import { AudioSource, AudioFrame } from '@dtelecom/server-sdk-node';\nimport { writeFileSync, appendFileSync, existsSync, mkdirSync } from 'fs';\nimport { join } from 'path';\nimport { createLogger } from '../utils/logger';\n\nconst log = createLogger('AudioOutput');\n\n/** Rate at which we write audio (48kHz mono, 20ms frames = 960 samples) */\nconst SAMPLE_RATE = 48000;\nconst CHANNELS = 1;\nconst FRAME_DURATION_MS = 20;\nconst SAMPLES_PER_FRAME = (SAMPLE_RATE * FRAME_DURATION_MS) / 1000; // 960 at 48kHz\n\n/** Pre-allocated silence frame */\nconst SILENCE = new Int16Array(SAMPLES_PER_FRAME);\n\nexport class AudioOutput {\n private source: AudioSource;\n private _playing = false;\n private _responding = false;\n private _stopped = false;\n private silenceInterval: ReturnType<typeof setInterval> | null = null;\n\n /** Resolves when the RTP transport is ready and initial silence has been sent. */\n readonly whenReady: Promise<void>;\n private _resolveReady?: () => void;\n\n /** When set, raw PCM from TTS is saved to this directory as WAV files for debugging. */\n dumpDir: string | null = null;\n private dumpCounter = 0;\n\n constructor(source: AudioSource) {\n this.source = source;\n if (source.ready) {\n this.whenReady = Promise.resolve();\n } else {\n this.whenReady = new Promise((resolve) => { this._resolveReady = resolve; });\n }\n }\n\n get playing(): boolean {\n return this._playing;\n }\n\n /**\n * Mark the start of a multi-sentence response.\n * Suppresses silence injection between sentences so partial frames\n * in AudioSource's buffer don't get corrupted by interleaved silence.\n */\n beginResponse(): void {\n this._responding = true;\n }\n\n /** Mark the end of a response — re-enable silence keepalive. */\n endResponse(): void {\n this._responding = false;\n }\n\n /**\n * Start sparse silence keepalive to prevent the SFU from dropping the track.\n * With Opus DTX enabled, the encoder handles silence natively — we only need\n * an occasional packet to keep the SSRC alive.\n *\n * Waits for the RTP transport to be ready before sending — no frames are\n * wasted before DTLS is connected.\n */\n startSilence(): void {\n if (this.silenceInterval) return;\n\n const startKeepalive = () => {\n log.debug('Transport ready — sending initial silence burst + starting 3s keepalive');\n\n // Send 300ms of silence so the SFU starts forwarding the track and\n // the client's jitter buffer is primed before real speech arrives.\n for (let i = 0; i < 15; i++) {\n this.sendSilenceFrame();\n }\n this._resolveReady?.();\n\n this.silenceInterval = setInterval(() => {\n if (!this._playing && !this._responding && !this._stopped) {\n this.sendSilenceFrame();\n }\n }, 3000);\n };\n\n if (this.source.ready) {\n startKeepalive();\n } else {\n log.debug('Waiting for transport before starting silence keepalive...');\n this.source.onReady = () => startKeepalive();\n }\n }\n\n private sendSilenceFrame(): void {\n const frame = new AudioFrame(SILENCE, SAMPLE_RATE, CHANNELS, SAMPLES_PER_FRAME);\n this.source.captureFrame(frame).catch((err) => {\n log.warn('Failed to send silence frame:', err);\n });\n }\n\n /**\n * Write a PCM16 buffer to the audio output.\n * The buffer is split into 20ms frames and fed to AudioSource.\n */\n async writeBuffer(pcm16: Buffer): Promise<void> {\n this._playing = true;\n try {\n await this.writeFrames(pcm16);\n } finally {\n this._playing = false;\n }\n }\n\n /**\n * Write a stream of PCM16 buffers (from TTS) to the audio output.\n * Supports cancellation via AbortSignal.\n */\n async writeStream(\n stream: AsyncIterable<Buffer>,\n signal?: AbortSignal,\n ): Promise<void> {\n this._playing = true;\n const streamStart = performance.now();\n let chunkCount = 0;\n let totalBytes = 0;\n\n log.debug('writeStream: started');\n\n // Collect raw TTS chunks for WAV dump if enabled\n const rawChunks: Buffer[] | null = this.dumpDir ? [] : null;\n\n try {\n for await (const chunk of stream) {\n if (signal?.aborted) {\n log.debug(`writeStream: cancelled after ${chunkCount} chunks, ${(performance.now() - streamStart).toFixed(0)}ms`);\n break;\n }\n chunkCount++;\n totalBytes += chunk.byteLength;\n rawChunks?.push(Buffer.from(chunk));\n await this.writeFrames(chunk);\n }\n } finally {\n this._playing = false;\n const elapsed = performance.now() - streamStart;\n const audioDurationMs = (totalBytes / 2) / SAMPLE_RATE * 1000;\n log.info(\n `writeStream: done — ${chunkCount} chunks, ${totalBytes} bytes, ` +\n `audio=${audioDurationMs.toFixed(0)}ms, wall=${elapsed.toFixed(0)}ms`,\n );\n\n // Save raw TTS audio as WAV for debugging\n if (rawChunks && rawChunks.length > 0 && this.dumpDir) {\n try {\n if (!existsSync(this.dumpDir)) mkdirSync(this.dumpDir, { recursive: true });\n const filePath = join(this.dumpDir, `tts-raw-${++this.dumpCounter}.wav`);\n writeWav(filePath, rawChunks, SAMPLE_RATE);\n log.info(`writeStream: saved raw TTS to ${filePath}`);\n } catch (err) {\n log.warn('writeStream: failed to save WAV dump:', err);\n }\n }\n }\n }\n\n /**\n * Split a PCM16 buffer into 20ms frames and write them at real-time pace.\n * Partial frames at the end are sent directly — AudioSource handles\n * accumulation in its internal buffer.\n */\n private async writeFrames(pcm16: Buffer): Promise<void> {\n // Ensure aligned buffer for Int16Array.\n // ws library may deliver Buffers with odd byteOffset.\n const aligned = Buffer.alloc(pcm16.byteLength);\n pcm16.copy(aligned);\n const samples = new Int16Array(\n aligned.buffer,\n aligned.byteOffset,\n aligned.byteLength / 2,\n );\n\n let offset = 0;\n while (offset < samples.length) {\n const end = Math.min(offset + SAMPLES_PER_FRAME, samples.length);\n const frameSamples = samples.subarray(offset, end);\n\n const frame = new AudioFrame(\n frameSamples,\n SAMPLE_RATE,\n CHANNELS,\n frameSamples.length,\n );\n\n await this.source.captureFrame(frame);\n\n // Only pace full frames — partial frames don't produce an Opus packet,\n // they just accumulate in AudioSource's buffer. Sleeping for them\n // causes audio to play slower than real-time.\n if (frameSamples.length === SAMPLES_PER_FRAME) {\n await sleep(FRAME_DURATION_MS);\n }\n\n offset = end;\n }\n }\n\n /**\n * Write silence frames for the given duration.\n * Used to pad the end of a response so the last Opus frame is fully flushed\n * and the audio doesn't cut off abruptly.\n */\n async writeSilence(durationMs: number): Promise<void> {\n const frameCount = Math.ceil(durationMs / FRAME_DURATION_MS);\n for (let i = 0; i < frameCount; i++) {\n const frame = new AudioFrame(SILENCE, SAMPLE_RATE, CHANNELS, SAMPLES_PER_FRAME);\n await this.source.captureFrame(frame);\n await sleep(FRAME_DURATION_MS);\n }\n }\n\n /** Flush any buffered audio in AudioSource */\n flush(): void {\n this.source.flush();\n this._playing = false;\n }\n\n /** Stop the silence keepalive */\n stop(): void {\n this._stopped = true;\n if (this.silenceInterval) {\n clearInterval(this.silenceInterval);\n this.silenceInterval = null;\n }\n }\n}\n\nfunction sleep(ms: number): Promise<void> {\n return new Promise((resolve) => setTimeout(resolve, ms));\n}\n\n/** Write a WAV file header + PCM data for debugging. */\nfunction writeWav(filePath: string, pcmChunks: Buffer[], sampleRate: number): void {\n const dataSize = pcmChunks.reduce((sum, b) => sum + b.byteLength, 0);\n const header = Buffer.alloc(44);\n header.write('RIFF', 0);\n header.writeUInt32LE(36 + dataSize, 4);\n header.write('WAVE', 8);\n header.write('fmt ', 12);\n header.writeUInt32LE(16, 16); // fmt chunk size\n header.writeUInt16LE(1, 20); // PCM format\n header.writeUInt16LE(1, 22); // mono\n header.writeUInt32LE(sampleRate, 24);\n header.writeUInt32LE(sampleRate * 2, 28); // byte rate\n header.writeUInt16LE(2, 32); // block align\n header.writeUInt16LE(16, 34); // bits per sample\n header.write('data', 36);\n header.writeUInt32LE(dataSize, 40);\n\n writeFileSync(filePath, header);\n for (const chunk of pcmChunks) {\n appendFileSync(filePath, chunk);\n }\n}\n","/**\n * Pipeline — coordinates the STT -> LLM -> TTS flow.\n *\n * Uses a producer/consumer pattern:\n * - Producer: LLM tokens -> sentence splitter -> sentence queue\n * - Consumer: sentence queue -> TTS -> audio output\n * Both run concurrently so audio playback never blocks LLM consumption.\n *\n * Supports barge-in (interruption cancels both producer and consumer).\n */\n\nimport { EventEmitter } from 'events';\nimport type {\n STTPlugin,\n STTStream,\n LLMPlugin,\n TTSPlugin,\n TranscriptionResult,\n RespondMode,\n PipelineOptions,\n PipelineEvents,\n AgentState,\n ToolDefinition,\n} from './types';\nimport { ContextManager } from './context-manager';\nimport { SentenceSplitter } from './sentence-splitter';\nimport { TurnDetector } from './turn-detector';\nimport { BargeIn } from './barge-in';\nimport type { AudioOutput } from '../room/audio-output';\nimport type { RoomMemory } from '../memory/room-memory';\nimport { createLogger } from '../utils/logger';\n\nconst log = createLogger('Pipeline');\n\n/**\n * Estimated latency from AudioSource.captureFrame() to the client hearing it:\n * Opus encode → RTP → SFU → client → jitter buffer → decode.\n * We delay the \"speaking: false\" emission by this amount so the UI status\n * matches what the user actually hears.\n */\nconst AUDIO_DRAIN_MS = 800;\n\nfunction sleep(ms: number): Promise<void> {\n return new Promise((resolve) => setTimeout(resolve, ms));\n}\n\n/**\n * Start TTS synthesis in the background, buffering chunks as they arrive.\n * Returns a factory that produces an async generator yielding the buffered chunks.\n * This allows the next sentence's TTS to run while the current sentence plays.\n */\nfunction prefetchTTS(\n tts: TTSPlugin,\n text: string,\n signal?: AbortSignal,\n): () => AsyncGenerator<Buffer> {\n const buffer: Buffer[] = [];\n let done = false;\n let error: unknown = null;\n let wake: (() => void) | null = null;\n\n const notify = () => { if (wake) { const w = wake; wake = null; w(); } };\n\n // Fire-and-forget: consume TTS into buffer immediately\n void (async () => {\n try {\n const stream = tts.synthesize(text, signal);\n for await (const chunk of stream) {\n if (signal?.aborted) break;\n buffer.push(chunk);\n notify();\n }\n } catch (e: unknown) {\n if (!(e instanceof Error && e.name === 'AbortError')) error = e;\n } finally {\n done = true;\n notify();\n }\n })();\n\n // Return factory for async generator that drains the buffer\n return async function* () {\n let index = 0;\n while (true) {\n if (signal?.aborted) return;\n if (error) throw error;\n if (index < buffer.length) {\n yield buffer[index++];\n continue;\n }\n if (done) return;\n await new Promise<void>((r) => { wake = r; });\n }\n };\n}\n\nexport class Pipeline extends EventEmitter {\n private readonly stt: STTPlugin;\n private readonly llm: LLMPlugin;\n private readonly tts: TTSPlugin | undefined;\n private readonly audioOutput: AudioOutput;\n private readonly context: ContextManager;\n private readonly turnDetector: TurnDetector;\n private readonly bargeIn: BargeIn;\n private readonly splitter: SentenceSplitter;\n private readonly respondMode: RespondMode;\n private readonly agentName: string;\n private readonly nameVariants: string[];\n private readonly beforeRespond?: (speaker: string, text: string) => boolean | Promise<boolean>;\n private readonly memory?: RoomMemory;\n private readonly tools?: ToolDefinition[];\n\n /** Strip provider-specific markup (e.g. SSML lang tags) for display. */\n private cleanText(text: string): string {\n return this.tts?.cleanText ? this.tts.cleanText(text) : text;\n }\n\n /** Active STT streams, keyed by participant identity */\n private sttStreams = new Map<string, STTStream>();\n\n private _processing = false;\n private _running = false;\n private _agentState: AgentState = 'idle';\n /** Queued turn while current one is still processing */\n private pendingTurn: { speaker: string; text: string } | null = null;\n\n constructor(options: PipelineOptions) {\n super();\n this.stt = options.stt;\n this.llm = options.llm;\n this.tts = options.tts;\n this.audioOutput = options.audioOutput;\n this.respondMode = options.respondMode ?? 'always';\n this.agentName = (options.agentName ?? 'assistant').toLowerCase();\n this.nameVariants = (options.nameVariants ?? []).map((n) => n.toLowerCase());\n this.beforeRespond = options.beforeRespond;\n this.memory = options.memory;\n this.tools = options.tools;\n this.context = new ContextManager({\n instructions: options.instructions,\n maxContextTokens: options.maxContextTokens,\n });\n this.turnDetector = new TurnDetector({\n silenceTimeoutMs: options.silenceTimeoutMs,\n });\n this.bargeIn = new BargeIn();\n this.splitter = new SentenceSplitter();\n\n this.turnDetector.onTurnEnd = () => {};\n\n this.bargeIn.onInterrupt = () => {\n this.audioOutput.flush();\n this.splitter.reset();\n this.setAgentState('idle');\n };\n\n // Warm up LLM and TTS separately so say() can start as soon as TTS is ready\n this._ttsWarmupPromise = this.tts?.warmup\n ? this.tts.warmup().catch((err: unknown) => { log.warn('TTS warmup failed (non-fatal):', err); })\n : Promise.resolve();\n this._llmWarmupPromise = this.llm.warmup\n ? this.llm.warmup(options.instructions).catch((err: unknown) => { log.warn('LLM warmup failed (non-fatal):', err); })\n : Promise.resolve();\n this._audioReadyPromise = this.audioOutput.whenReady;\n this._warmupPromise = Promise.all([this._ttsWarmupPromise, this._llmWarmupPromise, this._audioReadyPromise]).then(() => {});\n }\n\n private readonly _warmupPromise: Promise<void>;\n private readonly _ttsWarmupPromise: Promise<void>;\n private readonly _llmWarmupPromise: Promise<void>;\n private readonly _audioReadyPromise: Promise<void>;\n\n get processing(): boolean {\n return this._processing;\n }\n\n get running(): boolean {\n return this._running;\n }\n\n get agentState(): AgentState {\n return this._agentState;\n }\n\n private setAgentState(state: AgentState): void {\n if (this._agentState !== state) {\n this._agentState = state;\n this.emit('agentState', state);\n }\n }\n\n addParticipant(identity: string): STTStream {\n const existing = this.sttStreams.get(identity);\n if (existing) {\n existing.close();\n this.sttStreams.delete(identity);\n log.info(`Replacing STT stream for \"${identity}\"`);\n }\n\n const stream = this.stt.createStream();\n this.sttStreams.set(identity, stream);\n this._running = true;\n\n stream.on('transcription', (result) => {\n this.handleTranscription(identity, result);\n });\n\n stream.on('error', (error) => {\n log.error(`STT error for ${identity}:`, error);\n this.emit('error', error);\n });\n\n log.info(`STT stream started for participant \"${identity}\"`);\n return stream;\n }\n\n async removeParticipant(identity: string): Promise<void> {\n const stream = this.sttStreams.get(identity);\n if (stream) {\n await stream.close();\n this.sttStreams.delete(identity);\n log.info(`STT stream removed for participant \"${identity}\"`);\n }\n }\n\n async stop(): Promise<void> {\n this._running = false;\n this.turnDetector.reset();\n this.bargeIn.reset();\n this.splitter.reset();\n\n for (const [, stream] of this.sttStreams) {\n await stream.close();\n }\n this.sttStreams.clear();\n\n this.tts?.close?.();\n\n log.info('Pipeline stopped');\n }\n\n getContextManager(): ContextManager {\n return this.context;\n }\n\n /** Switch STT language on all active streams (e.g. for bilingual lessons). */\n setSTTLanguage(language: string, options?: { forceWhisper?: boolean }): void {\n for (const [identity, stream] of this.sttStreams) {\n if (stream.setLanguage) {\n stream.setLanguage(language, options);\n log.info(`STT language → ${language}${options?.forceWhisper ? ' (whisper)' : ''} for \"${identity}\"`);\n }\n }\n }\n\n private lastFinalAt = 0;\n private lastSttDuration = 0;\n\n private async handleTranscription(speaker: string, result: TranscriptionResult): Promise<void> {\n this.emit('transcription', { ...result, speaker });\n\n // Any interim (including empty VAD speech_start) → user is speaking\n if (!result.isFinal) {\n this.setAgentState('listening');\n }\n\n if (this.audioOutput.playing && result.text.trim().length > 0) {\n this.bargeIn.trigger();\n }\n\n if (result.isFinal && result.text.trim()) {\n const text = result.text.trim();\n this.lastFinalAt = performance.now();\n this.lastSttDuration = result.sttDuration ?? 0;\n\n // Store every turn to memory (async, non-blocking)\n this.memory?.storeTurn(speaker, text, false);\n\n if (await this.shouldRespond(speaker, text)) {\n this.processTurn(speaker, text);\n } else {\n log.info(`Not responding to \"${speaker}\": \"${text.slice(0, 60)}\" (mode=${this.respondMode})`);\n this.setAgentState('idle');\n }\n } else if (result.isFinal) {\n // Empty final or no text — user stopped speaking without a usable turn\n this.setAgentState('idle');\n }\n }\n\n /**\n * Determine if the agent should respond to this turn.\n * In 'always' mode: responds to everything.\n * In 'addressed' mode: only when agent name is mentioned + optional beforeRespond hook.\n */\n private async shouldRespond(speaker: string, text: string): Promise<boolean> {\n if (this.respondMode === 'always') return true;\n\n // Check if agent name or variants are mentioned\n const lower = text.toLowerCase();\n const nameMatch = lower.includes(this.agentName) ||\n this.nameVariants.some((v) => lower.includes(v));\n\n if (!nameMatch) return false;\n\n // If beforeRespond hook exists, let it decide\n if (this.beforeRespond) {\n return this.beforeRespond(speaker, text);\n }\n\n return true;\n }\n\n private async processTurn(speaker: string, text: string): Promise<void> {\n if (this._processing) {\n log.info(`Queuing turn (current still processing): \"${text}\"`);\n this.pendingTurn = { speaker, text };\n this.bargeIn.trigger();\n return;\n }\n\n this._processing = true;\n await this._warmupPromise;\n\n // ── Latency tracking ──\n const tSpeechEnd = this.lastFinalAt;\n const sttDuration = this.lastSttDuration;\n let tLlmFirstToken = 0;\n let tFirstSentence = 0;\n let tFirstAudioPlayed = 0;\n\n log.info(`Processing turn from \"${speaker}\": ${text}`);\n\n try {\n this.context.addUserTurn(speaker, text);\n\n if (this.context.shouldSummarize()) {\n await this.context.summarize(this.llm);\n }\n\n const signal = this.bargeIn.startCycle();\n\n // Search memory for relevant past context\n let memoryContext = '';\n if (this.memory) {\n try {\n memoryContext = await this.memory.searchRelevant(text);\n } catch (err) {\n log.warn('Memory search failed:', err);\n }\n }\n\n const messages = this.context.buildMessages(memoryContext || undefined);\n let fullResponse = '';\n\n this.setAgentState('thinking');\n\n // ── Producer/Consumer pattern ──\n const sentenceQueue: string[] = [];\n let producerDone = false;\n let wakeConsumer: (() => void) | null = null;\n\n const wake = () => { wakeConsumer?.(); };\n\n /** Push a sentence to the queue with first-sentence tracking. */\n let isFirstSentence = true;\n const pushSentence = (text: string) => {\n if (signal.aborted) return;\n if (isFirstSentence) {\n tFirstSentence = performance.now();\n isFirstSentence = false;\n log.info(`first_sentence: ${(tFirstSentence - tSpeechEnd).toFixed(0)}ms — \"${text.slice(0, 60)}\"`);\n }\n sentenceQueue.push(text);\n wake();\n };\n\n // ── Producer: consume LLM stream, split into sentences ──\n const MAX_LLM_RETRIES = 2;\n\n let toolCallEmitted = false;\n\n const producer = async () => {\n const defaultLang = this.tts?.defaultLanguage;\n\n for (let attempt = 0; attempt <= MAX_LLM_RETRIES; attempt++) {\n if (signal.aborted) break;\n\n if (attempt > 0) {\n log.warn(`LLM retry ${attempt}/${MAX_LLM_RETRIES}...`);\n this.splitter.reset();\n }\n\n let isFirstChunk = true;\n const segBuf: Array<{ lang: string; text: string }> = [];\n\n const flushSegments = () => {\n if (segBuf.length === 0) return;\n\n const combined = segBuf\n .map((s) =>\n s.lang !== defaultLang\n ? `<lang xml:lang=\"${s.lang}\">${s.text}</lang>`\n : s.text,\n )\n .join(' ');\n segBuf.length = 0;\n pushSentence(combined);\n };\n\n const llmStream = this.llm.chat(messages, signal, { tools: this.tools });\n try {\n while (!signal.aborted) {\n const { value: chunk, done } = await llmStream.next();\n if (done || !chunk) break;\n if (signal.aborted) break;\n\n if (chunk.type === 'segment' && chunk.segment) {\n // Structured output: accumulate segments, flush at sentence boundaries\n if (isFirstChunk) {\n tLlmFirstToken = performance.now();\n isFirstChunk = false;\n log.info(`llm_first_segment: ${(tLlmFirstToken - tSpeechEnd).toFixed(0)}ms`);\n }\n\n // Track clean text for context/memory (not JSON)\n if (fullResponse) fullResponse += ' ';\n fullResponse += chunk.segment.text;\n\n segBuf.push(chunk.segment);\n\n // Flush at sentence boundaries (.!? optionally followed by quotes/parens)\n if (/[.!?][\"'»)]*\\s*$/.test(chunk.segment.text)) {\n flushSegments();\n }\n } else if (chunk.type === 'token' && chunk.token) {\n // Plain text mode (no structured output)\n if (isFirstChunk) {\n tLlmFirstToken = performance.now();\n isFirstChunk = false;\n log.info(`llm_first_token: ${(tLlmFirstToken - tSpeechEnd).toFixed(0)}ms`);\n }\n\n fullResponse += chunk.token;\n\n const sentences = this.splitter.push(chunk.token);\n for (const sentence of sentences) {\n pushSentence(sentence);\n }\n } else if (chunk.type === 'tool_call' && chunk.toolCall) {\n log.info(`Tool call: ${chunk.toolCall.name}(${chunk.toolCall.arguments})`);\n toolCallEmitted = true;\n this.emit('toolCall', chunk.toolCall);\n }\n }\n } finally {\n await llmStream.return(undefined);\n }\n\n // Flush remaining text\n if (!signal.aborted) {\n flushSegments();\n const remaining = this.splitter.flush();\n if (remaining) {\n pushSentence(remaining);\n }\n\n if (fullResponse.trim() || toolCallEmitted) {\n break; // Got output or tool call — done\n }\n\n log.warn(`LLM produced no output (attempt ${attempt + 1}/${MAX_LLM_RETRIES + 1})`);\n }\n }\n\n producerDone = true;\n wake();\n };\n\n // ── Consumer: synthesize sentences and play audio ──\n // beginResponse/endResponse suppresses silence injection between\n // sentences so partial frames in AudioSource don't get corrupted.\n // Pre-fetches TTS for the next sentence while current one plays.\n const consumer = async () => {\n this.audioOutput.beginResponse();\n type Prefetched = { sentence: string; streamFn: () => AsyncGenerator<Buffer> };\n const state: { prefetched: Prefetched | null } = { prefetched: null };\n try {\n while (true) {\n if (signal.aborted) break;\n\n let sentence: string;\n let existingStream: AsyncGenerator<Buffer> | undefined;\n\n if (state.prefetched) {\n sentence = state.prefetched.sentence;\n existingStream = state.prefetched.streamFn();\n state.prefetched = null;\n } else if (sentenceQueue.length > 0) {\n sentence = sentenceQueue.shift()!;\n if (!/\\w/.test(sentence)) {\n log.debug(`Skipping non-word sentence: \"${sentence}\"`);\n continue;\n }\n existingStream = undefined;\n } else if (producerDone) {\n break;\n } else {\n await new Promise<void>((resolve) => {\n wakeConsumer = resolve;\n });\n wakeConsumer = null;\n continue;\n }\n\n // Pre-fetch next sentence TTS while current one plays\n // Skip prefetch for sequential TTS providers (single WebSocket)\n const canPrefetch = this.tts && !this.tts.sequential;\n const tryPrefetch = () => {\n if (state.prefetched || !canPrefetch) return;\n if (sentenceQueue.length > 0) {\n const next = sentenceQueue.shift()!;\n if (/\\w/.test(next)) {\n state.prefetched = { sentence: next, streamFn: prefetchTTS(this.tts!, next, signal) };\n }\n }\n };\n tryPrefetch();\n\n try {\n await this.synthesizeAndPlay(sentence, signal, (t) => {\n if (!tFirstAudioPlayed) {\n tFirstAudioPlayed = t;\n this.setAgentState('speaking');\n }\n this.emit('sentence', this.cleanText(sentence), sentence);\n tryPrefetch(); // also try when first audio arrives (more sentences may be ready)\n }, existingStream);\n } catch (ttsErr: unknown) {\n // TTS error on a prefetched sentence should not kill the turn —\n // previous sentences already played successfully.\n if (ttsErr instanceof Error && ttsErr.name === 'AbortError') throw ttsErr;\n log.warn(`TTS error for sentence (skipping): \"${sentence.slice(0, 40)}\"`, ttsErr);\n }\n }\n } finally {\n if (!signal.aborted) {\n await this.audioOutput.writeSilence(40);\n }\n this.audioOutput.endResponse();\n }\n };\n\n await Promise.all([producer(), consumer()]);\n\n // ── Latency summary ──\n // STT: last interim (≈ end of speech) → final transcript received\n // LLM: final transcript → first complete sentence (TTFT + accumulation)\n // TTS: first sentence ready → first audio chunk to WebRTC\n // Overall: STT + LLM + TTS\n const ttftMs = tLlmFirstToken ? tLlmFirstToken - tSpeechEnd : 0;\n const llmMs = tFirstSentence ? tFirstSentence - tSpeechEnd : 0;\n const ttsMs = tFirstAudioPlayed && tFirstSentence ? tFirstAudioPlayed - tFirstSentence : 0;\n const overallMs = sttDuration + llmMs + ttsMs;\n\n log.info(\n `LATENCY \"${text.slice(0, 30)}\": ` +\n `STT=${sttDuration.toFixed(0)}ms ` +\n `LLM=${llmMs.toFixed(0)}ms (TTFT=${ttftMs.toFixed(0)}ms) ` +\n `TTS=${ttsMs.toFixed(0)}ms ` +\n `Overall=${overallMs.toFixed(0)}ms`,\n );\n\n if (fullResponse.trim()) {\n this.context.addAgentTurn(fullResponse.trim());\n this.memory?.storeTurn('assistant', fullResponse.trim(), true);\n this.emit('response', this.cleanText(fullResponse.trim()));\n }\n\n // Wait for audio pipeline to drain before signaling \"listening\"\n // (AudioSource → Opus → RTP → SFU → client decode)\n await sleep(AUDIO_DRAIN_MS);\n this.setAgentState('idle');\n } catch (err: unknown) {\n if (err instanceof Error && err.name === 'AbortError') {\n log.debug('Turn processing aborted (barge-in)');\n } else {\n log.error('Error processing turn:', err);\n this.emit('error', err instanceof Error ? err : new Error(String(err)));\n }\n } finally {\n this._processing = false;\n this.bargeIn.reset();\n\n if (this.pendingTurn) {\n const { speaker: nextSpeaker, text: nextText } = this.pendingTurn;\n this.pendingTurn = null;\n log.info(`Processing queued turn from \"${nextSpeaker}\": ${nextText}`);\n this.processTurn(nextSpeaker, nextText);\n }\n }\n }\n\n /**\n * Speak text directly via TTS, bypassing the LLM.\n * Supports barge-in — if a participant speaks, the playback is cut short.\n * Adds the text to conversation context so the LLM knows what was said.\n */\n async say(text: string): Promise<void> {\n if (this._processing) {\n log.warn('say() called while processing — skipping');\n return;\n }\n\n this._processing = true;\n await Promise.all([this._ttsWarmupPromise, this._audioReadyPromise]);\n log.info(`say(): \"${text.slice(0, 60)}\"`);\n\n try {\n const signal = this.bargeIn.startCycle();\n this.audioOutput.beginResponse();\n this.setAgentState('thinking');\n\n await this.synthesizeAndPlay(text, signal, () => {\n this.setAgentState('speaking');\n this.emit('sentence', this.cleanText(text), text);\n });\n\n if (!signal.aborted) {\n await this.audioOutput.writeSilence(40);\n this.context.addAgentTurn(text);\n this.memory?.storeTurn('assistant', text, true);\n this.emit('response', this.cleanText(text));\n }\n\n // Wait for audio pipeline to drain before signaling \"listening\"\n await sleep(AUDIO_DRAIN_MS);\n this.setAgentState('idle');\n } catch (err: unknown) {\n if (err instanceof Error && err.name === 'AbortError') {\n log.debug('say() aborted (barge-in)');\n } else {\n log.error('Error in say():', err);\n this.emit('error', err instanceof Error ? err : new Error(String(err)));\n }\n } finally {\n this._processing = false;\n this.audioOutput.endResponse();\n this.bargeIn.reset();\n\n if (this.pendingTurn) {\n const { speaker: nextSpeaker, text: nextText } = this.pendingTurn;\n this.pendingTurn = null;\n log.info(`Processing queued turn from \"${nextSpeaker}\": ${nextText}`);\n this.processTurn(nextSpeaker, nextText);\n }\n }\n }\n\n private async synthesizeAndPlay(\n text: string,\n signal: AbortSignal,\n onFirstAudio: (timestamp: number) => void,\n existingStream?: AsyncGenerator<Buffer>,\n ): Promise<void> {\n if (!this.tts || signal.aborted) {\n log.info(`[Agent says]: ${text}`);\n return;\n }\n\n try {\n const ttsStart = performance.now();\n let firstChunk = true;\n let ttsChunkCount = 0;\n\n const ttsStream = existingStream ?? this.tts.synthesize(text, signal);\n const measuredStream = async function* () {\n for await (const chunk of ttsStream) {\n ttsChunkCount++;\n if (firstChunk) {\n firstChunk = false;\n const now = performance.now();\n log.info(`tts_first_audio: ${(now - ttsStart).toFixed(0)}ms for \"${text.slice(0, 40)}\"`);\n onFirstAudio(now);\n }\n yield chunk;\n }\n };\n\n await this.audioOutput.writeStream(measuredStream(), signal);\n log.info(`synthesizeAndPlay done: ${(performance.now() - ttsStart).toFixed(0)}ms, ${ttsChunkCount} chunks for \"${text.slice(0, 40)}\"`);\n } catch (err: unknown) {\n if (err instanceof Error && err.name === 'AbortError') return;\n throw err;\n }\n }\n}\n","import type { Message, LLMPlugin } from './types';\nimport { createLogger } from '../utils/logger';\n\nconst log = createLogger('ContextManager');\n\n/** Rough token estimate: 1 token ~ 4 chars */\nfunction estimateTokens(text: string): number {\n return Math.ceil(text.length / 4);\n}\n\nexport interface ContextManagerOptions {\n /** System instructions for the agent */\n instructions: string;\n /** Max tokens before triggering summarization (default: 5000) */\n maxContextTokens?: number;\n /** Number of recent turns to keep verbatim (default: 4) */\n recentTurnsToKeep?: number;\n}\n\ninterface Turn {\n speaker: string;\n text: string;\n isAgent: boolean;\n timestamp: number;\n}\n\nexport class ContextManager {\n private readonly instructions: string;\n private readonly maxContextTokens: number;\n private readonly recentTurnsToKeep: number;\n\n private turns: Turn[] = [];\n private summary: string | null = null;\n\n constructor(options: ContextManagerOptions) {\n this.instructions = options.instructions;\n this.maxContextTokens = options.maxContextTokens ?? 5000;\n this.recentTurnsToKeep = options.recentTurnsToKeep ?? 4;\n }\n\n /** Add a user's speech turn to the conversation */\n addUserTurn(speaker: string, text: string): void {\n this.turns.push({\n speaker,\n text,\n isAgent: false,\n timestamp: Date.now(),\n });\n }\n\n /** Add the agent's response to the conversation */\n addAgentTurn(text: string): void {\n this.turns.push({\n speaker: 'assistant',\n text,\n isAgent: true,\n timestamp: Date.now(),\n });\n }\n\n /**\n * Build the messages array for the LLM call.\n *\n * Structure:\n * [system prompt]\n * [memory context, if provided]\n * [conversation summary, if any]\n * [recent verbatim turns]\n *\n * @param memoryContext - Optional relevant context injected by the application\n */\n buildMessages(memoryContext?: string): Message[] {\n const messages: Message[] = [];\n\n // System prompt\n messages.push({ role: 'system', content: this.instructions });\n\n // Application-injected memory context (if available)\n if (memoryContext) {\n messages.push({\n role: 'system',\n content: `Relevant context from past conversations:\\n${memoryContext}`,\n });\n }\n\n // Conversation summary (if summarization has occurred)\n if (this.summary) {\n messages.push({\n role: 'system',\n content: `Conversation summary so far:\\n${this.summary}`,\n });\n }\n\n // Format turns as messages\n const turnsToInclude = this.summary\n ? this.turns.slice(-this.recentTurnsToKeep)\n : this.turns;\n\n for (const turn of turnsToInclude) {\n if (turn.isAgent) {\n messages.push({ role: 'assistant', content: turn.text });\n } else {\n messages.push({\n role: 'user',\n content: `[${turn.speaker}]: ${turn.text}`,\n });\n }\n }\n\n return messages;\n }\n\n /** Check if summarization should be triggered */\n shouldSummarize(): boolean {\n const totalTokens = this.turns.reduce(\n (acc, t) => acc + estimateTokens(t.text) + 10,\n estimateTokens(this.instructions),\n );\n return totalTokens > this.maxContextTokens;\n }\n\n /**\n * Summarize older turns using the LLM.\n * Keeps the most recent turns verbatim.\n */\n async summarize(llm: LLMPlugin): Promise<void> {\n if (this.turns.length <= this.recentTurnsToKeep) return;\n\n const olderTurns = this.turns.slice(0, -this.recentTurnsToKeep);\n const transcript = olderTurns\n .map((t) => `[${t.speaker}]: ${t.text}`)\n .join('\\n');\n\n const summaryPrompt: Message[] = [\n {\n role: 'system',\n content: 'Summarize this conversation concisely, preserving key facts, decisions, and action items.',\n },\n { role: 'user', content: transcript },\n ];\n\n let summaryText = '';\n for await (const chunk of llm.chat(summaryPrompt)) {\n if (chunk.type === 'token' && chunk.token) {\n summaryText += chunk.token;\n }\n }\n\n this.summary = this.summary\n ? `${this.summary}\\n\\n${summaryText}`\n : summaryText;\n\n this.turns = this.turns.slice(-this.recentTurnsToKeep);\n\n log.info(`Summarized ${olderTurns.length} turns, ${this.turns.length} recent turns kept`);\n }\n\n /** Get the full transcript */\n getFullTranscript(): string {\n return this.turns.map((t) => `[${t.speaker}]: ${t.text}`).join('\\n');\n }\n\n /** Reset the context */\n reset(): void {\n this.turns = [];\n this.summary = null;\n }\n}\n","/**\n * SentenceSplitter — buffers streaming LLM tokens into speakable chunks\n * for TTS synthesis.\n *\n * Split strategy:\n * 1. Sentence boundary (.!?) — always split\n * 2. Clause boundary (,;:—) — split if buffer >= MIN_CHUNK chars\n * 3. Word boundary — forced split if buffer >= MAX_CHUNK chars\n */\n\nconst MIN_CHUNK = 20;\nconst MAX_CHUNK = 150;\n\nexport class SentenceSplitter {\n private buffer = '';\n\n /** Add a token and get back any speakable chunks */\n push(token: string): string[] {\n this.buffer += token;\n return this.extractChunks();\n }\n\n /** Flush any remaining text as a final chunk */\n flush(): string | null {\n const text = this.buffer.trim();\n this.buffer = '';\n return text.length > 0 ? text : null;\n }\n\n /** Reset the splitter */\n reset(): void {\n this.buffer = '';\n }\n\n private extractChunks(): string[] {\n const chunks: string[] = [];\n\n while (true) {\n // 1. Sentence boundary (.!?) — split on complete sentences\n const sentenceMatch = this.buffer.match(/[^.!?]*[.!?]\\s*/);\n if (sentenceMatch && sentenceMatch.index !== undefined) {\n const end = sentenceMatch.index + sentenceMatch[0].length;\n const chunk = this.buffer.slice(0, end).trim();\n if (chunk.length >= MIN_CHUNK) {\n chunks.push(chunk);\n this.buffer = this.buffer.slice(end);\n continue;\n }\n }\n\n // 2. Clause boundary (,;:—) if buffer is getting long\n if (this.buffer.length >= MAX_CHUNK) {\n const clauseMatch = this.buffer.match(/[,;:\\u2014]\\s*/);\n if (clauseMatch && clauseMatch.index !== undefined && clauseMatch.index >= MIN_CHUNK) {\n const end = clauseMatch.index + clauseMatch[0].length;\n const chunk = this.buffer.slice(0, end).trim();\n chunks.push(chunk);\n this.buffer = this.buffer.slice(end);\n continue;\n }\n\n // 3. Word boundary — forced split\n const spaceIdx = this.buffer.lastIndexOf(' ', MAX_CHUNK);\n if (spaceIdx >= MIN_CHUNK) {\n const chunk = this.buffer.slice(0, spaceIdx).trim();\n chunks.push(chunk);\n this.buffer = this.buffer.slice(spaceIdx);\n continue;\n }\n }\n\n break;\n }\n\n return chunks;\n }\n}\n","import { createLogger } from '../utils/logger';\n\nconst log = createLogger('TurnDetector');\n\nexport interface TurnDetectorOptions {\n /** Silence duration after final transcription before triggering (default: 800ms) */\n silenceTimeoutMs?: number;\n}\n\nexport class TurnDetector {\n private readonly silenceTimeoutMs: number;\n private silenceTimer: ReturnType<typeof setTimeout> | null = null;\n private _onTurnEnd: (() => void) | null = null;\n private lastFinalText = '';\n\n constructor(options: TurnDetectorOptions = {}) {\n this.silenceTimeoutMs = options.silenceTimeoutMs ?? 800;\n }\n\n /** Set the callback for when a turn ends */\n set onTurnEnd(cb: (() => void) | null) {\n this._onTurnEnd = cb;\n }\n\n /**\n * Feed a transcription result.\n * Returns true if this result represents a completed turn.\n */\n handleTranscription(text: string, isFinal: boolean): boolean {\n this.clearTimer();\n\n if (isFinal && text.trim().length > 0) {\n this.lastFinalText = text;\n\n // Start silence timer — if no new speech, the turn is done\n this.silenceTimer = setTimeout(() => {\n log.debug(`Turn ended after ${this.silenceTimeoutMs}ms silence`);\n this._onTurnEnd?.();\n }, this.silenceTimeoutMs);\n\n return false;\n }\n\n if (!isFinal && text.trim().length > 0) {\n // Interim result — user is still speaking, reset timer\n this.clearTimer();\n }\n\n return false;\n }\n\n /** Force-trigger turn end */\n forceTurnEnd(): void {\n this.clearTimer();\n this._onTurnEnd?.();\n }\n\n /** Reset state */\n reset(): void {\n this.clearTimer();\n this.lastFinalText = '';\n }\n\n private clearTimer(): void {\n if (this.silenceTimer) {\n clearTimeout(this.silenceTimer);\n this.silenceTimer = null;\n }\n }\n}\n","import { createLogger } from '../utils/logger';\n\nconst log = createLogger('BargeIn');\n\nexport class BargeIn {\n private abortController: AbortController | null = null;\n private _interrupted = false;\n private _onInterrupt: (() => void) | null = null;\n\n get interrupted(): boolean {\n return this._interrupted;\n }\n\n /** Set the callback for when barge-in occurs */\n set onInterrupt(cb: (() => void) | null) {\n this._onInterrupt = cb;\n }\n\n /**\n * Create a new AbortController for the current response cycle.\n * Call this at the start of each STT->LLM->TTS cycle.\n */\n startCycle(): AbortSignal {\n this.abortController = new AbortController();\n this._interrupted = false;\n return this.abortController.signal;\n }\n\n /** Trigger barge-in. Called when STT detects speech during agent output. */\n trigger(): void {\n if (this._interrupted) return;\n this._interrupted = true;\n\n log.info('Barge-in detected — cancelling current response');\n\n if (this.abortController) {\n this.abortController.abort();\n this.abortController = null;\n }\n\n this._onInterrupt?.();\n }\n\n /** Reset after the interrupted cycle is cleaned up */\n reset(): void {\n this._interrupted = false;\n this.abortController = null;\n }\n}\n"],"mappings":"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","names":["EventEmitter","log","log","log","log","log","log","sleep","text","log","EventEmitter"]}
@@ -1,4 +1,4 @@
1
- export { E as Embedder, n as RoomMemory, o as RoomMemoryConfig } from '../types-BJylZd8Q.mjs';
1
+ export { E as Embedder, n as RoomMemory, o as RoomMemoryConfig } from '../types-DNVsSPb4.mjs';
2
2
  import '@dtelecom/server-sdk-node';
3
3
 
4
4
  /**
@@ -1,4 +1,4 @@
1
- export { E as Embedder, n as RoomMemory, o as RoomMemoryConfig } from '../types-BJylZd8Q.js';
1
+ export { E as Embedder, n as RoomMemory, o as RoomMemoryConfig } from '../types-DNVsSPb4.js';
2
2
  import '@dtelecom/server-sdk-node';
3
3
 
4
4
  /**
@@ -1,4 +1,4 @@
1
- import { i as STTPlugin, j as STTStreamOptions, S as STTStream, L as LLMPlugin, M as Message, e as LLMChatOptions, f as LLMChunk, k as TTSPlugin } from '../types-BJylZd8Q.mjs';
1
+ import { i as STTPlugin, j as STTStreamOptions, S as STTStream, L as LLMPlugin, M as Message, e as LLMChatOptions, f as LLMChunk, k as TTSPlugin } from '../types-DNVsSPb4.mjs';
2
2
  import '@dtelecom/server-sdk-node';
3
3
 
4
4
  /**
@@ -313,8 +313,8 @@ declare class DtelecomTTS implements TTSPlugin {
313
313
  private ws;
314
314
  private connectPromise;
315
315
  private flushState;
316
- /** Serializes synthesizeSegment calls so flushState is never overwritten mid-stream. */
317
- private _synthLock;
316
+ /** Single WebSocket pipeline must not prefetch TTS concurrently. */
317
+ readonly sequential = true;
318
318
  /** Default language code for untagged text (e.g. 'en'). */
319
319
  get defaultLanguage(): string;
320
320
  constructor(options: DtelecomTTSOptions);
@@ -1,4 +1,4 @@
1
- import { i as STTPlugin, j as STTStreamOptions, S as STTStream, L as LLMPlugin, M as Message, e as LLMChatOptions, f as LLMChunk, k as TTSPlugin } from '../types-BJylZd8Q.js';
1
+ import { i as STTPlugin, j as STTStreamOptions, S as STTStream, L as LLMPlugin, M as Message, e as LLMChatOptions, f as LLMChunk, k as TTSPlugin } from '../types-DNVsSPb4.js';
2
2
  import '@dtelecom/server-sdk-node';
3
3
 
4
4
  /**
@@ -313,8 +313,8 @@ declare class DtelecomTTS implements TTSPlugin {
313
313
  private ws;
314
314
  private connectPromise;
315
315
  private flushState;
316
- /** Serializes synthesizeSegment calls so flushState is never overwritten mid-stream. */
317
- private _synthLock;
316
+ /** Single WebSocket pipeline must not prefetch TTS concurrently. */
317
+ readonly sequential = true;
318
318
  /** Default language code for untagged text (e.g. 'en'). */
319
319
  get defaultLanguage(): string;
320
320
  constructor(options: DtelecomTTSOptions);
@@ -1389,8 +1389,8 @@ var DtelecomTTS = class {
1389
1389
  ws = null;
1390
1390
  connectPromise = null;
1391
1391
  flushState = null;
1392
- /** Serializes synthesizeSegment calls so flushState is never overwritten mid-stream. */
1393
- _synthLock = Promise.resolve();
1392
+ /** Single WebSocket pipeline must not prefetch TTS concurrently. */
1393
+ sequential = true;
1394
1394
  /** Default language code for untagged text (e.g. 'en'). */
1395
1395
  get defaultLanguage() {
1396
1396
  return this.defaultLang;
@@ -1462,18 +1462,18 @@ var DtelecomTTS = class {
1462
1462
  }
1463
1463
  }
1464
1464
  async *synthesizeSegment(lang, text, signal) {
1465
- const prevLock = this._synthLock;
1466
- let releaseLock;
1467
- this._synthLock = new Promise((r) => {
1468
- releaseLock = r;
1469
- });
1470
- await prevLock;
1465
+ if (signal?.aborted) return;
1471
1466
  await this.ensureConnection();
1472
1467
  const ws = this.ws;
1473
1468
  if (!ws || ws.readyState !== import_ws5.default.OPEN) {
1474
- releaseLock();
1475
1469
  throw new Error("dTelecom TTS WebSocket not connected");
1476
1470
  }
1471
+ if (this.flushState) {
1472
+ this.flushState.done = true;
1473
+ this.flushState.wake?.();
1474
+ ws.send(JSON.stringify({ type: "clear" }));
1475
+ this.flushState = null;
1476
+ }
1477
1477
  const state = { chunks: [], done: false, cleared: false, error: null, wake: null };
1478
1478
  this.flushState = state;
1479
1479
  const onAbort = () => {
@@ -1516,7 +1516,6 @@ var DtelecomTTS = class {
1516
1516
  } finally {
1517
1517
  signal?.removeEventListener("abort", onAbort);
1518
1518
  this.flushState = null;
1519
- releaseLock();
1520
1519
  }
1521
1520
  }
1522
1521
  /** Ensure a WebSocket connection exists and is open. */