@dtelecom/agents-js 0.1.0 → 0.1.2
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.d.mts +3 -69
- package/dist/index.d.ts +3 -69
- package/dist/index.js +1 -10
- package/dist/index.js.map +1 -1
- package/dist/index.mjs +3 -11
- package/dist/index.mjs.map +1 -1
- package/dist/memory/index.d.mts +70 -0
- package/dist/memory/index.d.ts +70 -0
- package/dist/memory/index.js +486 -0
- package/dist/memory/index.js.map +1 -0
- package/dist/memory/index.mjs +12 -0
- package/dist/memory/index.mjs.map +1 -0
- package/dist/providers/index.d.mts +1 -1
- package/dist/providers/index.d.ts +1 -1
- package/dist/{types-Cs5uUoTC.d.mts → types-EvtHMokR.d.mts} +1 -1
- package/dist/{types-Cs5uUoTC.d.ts → types-EvtHMokR.d.ts} +1 -1
- package/package.json +6 -1
package/dist/index.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"sources":["../src/utils/logger.ts","../src/memory/memory-store.ts","../src/memory/embedder.ts","../src/memory/room-memory.ts","../src/index.ts","../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","../src/core/base-stt-stream.ts"],"sourcesContent":["export type LogLevel = 'debug' | 'info' | 'warn' | 'error' | 'silent';\n\nconst LEVELS: Record<LogLevel, number> = {\n debug: 0,\n info: 1,\n warn: 2,\n error: 3,\n silent: 4,\n};\n\n/** Default to 'debug' if DEBUG env var matches our namespace */\nfunction detectLevel(): LogLevel {\n const debug = typeof process !== 'undefined' && process.env?.DEBUG;\n if (debug && (debug === '*' || debug.includes('@dtelecom/agents'))) {\n return 'debug';\n }\n return 'info';\n}\n\nlet globalLevel: LogLevel = detectLevel();\n\nexport function setLogLevel(level: LogLevel): void {\n globalLevel = level;\n}\n\nexport function getLogLevel(): LogLevel {\n return globalLevel;\n}\n\nexport interface Logger {\n debug(...args: unknown[]): void;\n info(...args: unknown[]): void;\n warn(...args: unknown[]): void;\n error(...args: unknown[]): void;\n}\n\nfunction timestamp(): string {\n const d = new Date();\n const h = String(d.getHours()).padStart(2, '0');\n const m = String(d.getMinutes()).padStart(2, '0');\n const s = String(d.getSeconds()).padStart(2, '0');\n const ms = String(d.getMilliseconds()).padStart(3, '0');\n return `${h}:${m}:${s}.${ms}`;\n}\n\nexport function createLogger(tag: string): Logger {\n const prefix = `[@dtelecom/agents:${tag}]`;\n return {\n debug(...args: unknown[]) {\n if (LEVELS[globalLevel] <= LEVELS.debug) console.debug(timestamp(), prefix, ...args);\n },\n info(...args: unknown[]) {\n if (LEVELS[globalLevel] <= LEVELS.info) console.info(timestamp(), prefix, ...args);\n },\n warn(...args: unknown[]) {\n if (LEVELS[globalLevel] <= LEVELS.warn) console.warn(timestamp(), prefix, ...args);\n },\n error(...args: unknown[]) {\n if (LEVELS[globalLevel] <= LEVELS.error) console.error(timestamp(), prefix, ...args);\n },\n };\n}\n","/**\n * MemoryStore — SQLite + sqlite-vec database layer for room memory.\n *\n * Single .db file stores:\n * - turns: every spoken turn (full transcript)\n * - sessions: meeting metadata + LLM-generated summaries\n * - turn_vectors: embedding index for semantic turn search\n * - session_vectors: embedding index for session summary search\n */\n\nimport Database from 'better-sqlite3';\nimport * as sqliteVec from 'sqlite-vec';\nimport { createLogger } from '../utils/logger';\n\nconst log = createLogger('MemoryStore');\n\nexport interface TurnRow {\n id: number;\n room: string;\n session_id: string;\n speaker: string;\n text: string;\n is_agent: number;\n created_at: number;\n}\n\nexport interface SessionRow {\n id: string;\n room: string;\n started_at: number;\n ended_at: number | null;\n participants: string | null;\n summary: string | null;\n turn_count: number;\n}\n\nexport interface SearchResult {\n speaker: string;\n text: string;\n created_at: number;\n session_id: string;\n distance: number;\n}\n\nexport interface SessionSearchResult {\n session_id: string;\n summary: string;\n started_at: number;\n distance: number;\n}\n\nexport class MemoryStore {\n private db: Database.Database;\n\n constructor(dbPath: string) {\n this.db = new Database(dbPath);\n this.db.pragma('journal_mode = WAL');\n this.db.pragma('synchronous = NORMAL');\n\n // Load sqlite-vec extension\n sqliteVec.load(this.db);\n\n this.createTables();\n log.info(`Memory store opened: ${dbPath}`);\n }\n\n private createTables(): void {\n this.db.exec(`\n CREATE TABLE IF NOT EXISTS turns (\n id INTEGER PRIMARY KEY AUTOINCREMENT,\n room TEXT NOT NULL,\n session_id TEXT NOT NULL,\n speaker TEXT NOT NULL,\n text TEXT NOT NULL,\n is_agent BOOLEAN DEFAULT 0,\n created_at INTEGER NOT NULL\n );\n\n CREATE TABLE IF NOT EXISTS sessions (\n id TEXT PRIMARY KEY,\n room TEXT NOT NULL,\n started_at INTEGER NOT NULL,\n ended_at INTEGER,\n participants TEXT,\n summary TEXT,\n turn_count INTEGER DEFAULT 0\n );\n\n CREATE INDEX IF NOT EXISTS idx_turns_room_session ON turns(room, session_id);\n CREATE INDEX IF NOT EXISTS idx_turns_room_time ON turns(room, created_at);\n CREATE INDEX IF NOT EXISTS idx_sessions_room ON sessions(room);\n `);\n\n // Vector tables — sqlite-vec virtual tables\n // Check if they exist first (CREATE VIRTUAL TABLE doesn't support IF NOT EXISTS)\n const hasVecTable = this.db.prepare(\n \"SELECT name FROM sqlite_master WHERE type='table' AND name=?\",\n );\n\n if (!hasVecTable.get('turn_vectors')) {\n this.db.exec(`\n CREATE VIRTUAL TABLE turn_vectors USING vec0(\n turn_id INTEGER PRIMARY KEY,\n embedding FLOAT[384] distance_metric=cosine\n );\n `);\n }\n\n if (!hasVecTable.get('session_vectors')) {\n this.db.exec(`\n CREATE VIRTUAL TABLE session_vectors USING vec0(\n session_id TEXT PRIMARY KEY,\n embedding FLOAT[384] distance_metric=cosine\n );\n `);\n }\n }\n\n /** Insert a turn and its embedding vector. */\n insertTurn(\n room: string,\n sessionId: string,\n speaker: string,\n text: string,\n isAgent: boolean,\n embedding: Float32Array,\n ): number {\n const stmt = this.db.prepare(`\n INSERT INTO turns (room, session_id, speaker, text, is_agent, created_at)\n VALUES (?, ?, ?, ?, ?, ?)\n `);\n const info = stmt.run(room, sessionId, speaker, text, isAgent ? 1 : 0, Date.now());\n const turnId = info.lastInsertRowid;\n\n // Insert embedding vector — sqlite-vec requires BigInt for integer PKs\n this.db.prepare(\n 'INSERT INTO turn_vectors (turn_id, embedding) VALUES (?, ?)',\n ).run(BigInt(turnId), Buffer.from(embedding.buffer));\n\n return Number(turnId);\n }\n\n /** Create a new session record. */\n insertSession(id: string, room: string): void {\n this.db.prepare(`\n INSERT INTO sessions (id, room, started_at)\n VALUES (?, ?, ?)\n `).run(id, room, Date.now());\n }\n\n /** Update a session with summary and end time. */\n updateSessionSummary(\n sessionId: string,\n summary: string,\n turnCount: number,\n participants: string[],\n embedding: Float32Array,\n ): void {\n this.db.prepare(`\n UPDATE sessions\n SET summary = ?, ended_at = ?, turn_count = ?, participants = ?\n WHERE id = ?\n `).run(summary, Date.now(), turnCount, JSON.stringify(participants), sessionId);\n\n // Insert summary embedding\n this.db.prepare(\n 'INSERT INTO session_vectors (session_id, embedding) VALUES (?, ?)',\n ).run(sessionId, Buffer.from(embedding.buffer));\n }\n\n /** End a session without summary (e.g., too few turns). */\n endSession(sessionId: string, turnCount: number, participants: string[]): void {\n this.db.prepare(`\n UPDATE sessions\n SET ended_at = ?, turn_count = ?, participants = ?\n WHERE id = ?\n `).run(Date.now(), turnCount, JSON.stringify(participants), sessionId);\n }\n\n /** KNN search turns by embedding similarity. */\n searchTurns(room: string, queryEmbedding: Float32Array, limit: number): SearchResult[] {\n const rows = this.db.prepare(`\n SELECT t.speaker, t.text, t.created_at, t.session_id, tv.distance\n FROM turn_vectors tv\n JOIN turns t ON t.id = tv.turn_id\n WHERE t.room = ?\n AND tv.embedding MATCH ?\n AND k = ?\n ORDER BY tv.distance\n `).all(room, Buffer.from(queryEmbedding.buffer), limit * 2) as (TurnRow & { distance: number })[];\n\n // sqlite-vec returns k results from the vector index, then we filter by room\n return rows.slice(0, limit).map((r) => ({\n speaker: r.speaker,\n text: r.text,\n created_at: r.created_at,\n session_id: r.session_id,\n distance: r.distance,\n }));\n }\n\n /** KNN search session summaries by embedding similarity. */\n searchSessions(room: string, queryEmbedding: Float32Array, limit: number): SessionSearchResult[] {\n const rows = this.db.prepare(`\n SELECT s.id as session_id, s.summary, s.started_at, sv.distance\n FROM session_vectors sv\n JOIN sessions s ON s.id = sv.session_id\n WHERE s.room = ?\n AND sv.embedding MATCH ?\n AND k = ?\n ORDER BY sv.distance\n `).all(room, Buffer.from(queryEmbedding.buffer), limit * 2) as SessionSearchResult[];\n\n return rows\n .filter((r) => r.summary)\n .slice(0, limit);\n }\n\n /** Get the last N turns from a specific session. */\n getRecentTurns(room: string, sessionId: string, limit: number): TurnRow[] {\n return this.db.prepare(`\n SELECT * FROM turns\n WHERE room = ? AND session_id = ?\n ORDER BY created_at DESC\n LIMIT ?\n `).all(room, sessionId, limit) as TurnRow[];\n }\n\n /** Get all turns for a session (for summarization). */\n getSessionTurns(sessionId: string): TurnRow[] {\n return this.db.prepare(`\n SELECT * FROM turns\n WHERE session_id = ?\n ORDER BY created_at ASC\n `).all(sessionId) as TurnRow[];\n }\n\n /** Get total turn count for a session. */\n getSessionTurnCount(sessionId: string): number {\n const row = this.db.prepare(\n 'SELECT COUNT(*) as count FROM turns WHERE session_id = ?',\n ).get(sessionId) as { count: number };\n return row.count;\n }\n\n /** Close the database. */\n close(): void {\n this.db.close();\n log.info('Memory store closed');\n }\n}\n","/**\n * Embedder — local text embedding via @huggingface/transformers.\n *\n * Uses Xenova/all-MiniLM-L6-v2 (384 dimensions, ~22MB model).\n * Runs entirely in-process — no API calls, no cost.\n */\n\nimport { createLogger } from '../utils/logger';\n\nconst log = createLogger('Embedder');\n\nconst MODEL_NAME = 'Xenova/all-MiniLM-L6-v2';\nconst EMBEDDING_DIM = 384;\n\ntype FeatureExtractionPipeline = (\n text: string | string[],\n options?: { pooling?: string; normalize?: boolean },\n) => Promise<{ data: Float32Array }>;\n\nexport class Embedder {\n private pipeline: FeatureExtractionPipeline | null = null;\n private initPromise: Promise<void> | null = null;\n\n get dimensions(): number {\n return EMBEDDING_DIM;\n }\n\n /** Load the embedding model. Call once at startup. */\n async init(): Promise<void> {\n if (this.pipeline) return;\n if (this.initPromise) return this.initPromise;\n\n this.initPromise = this.loadModel();\n return this.initPromise;\n }\n\n private async loadModel(): Promise<void> {\n const start = performance.now();\n log.info(`Loading embedding model \"${MODEL_NAME}\"...`);\n\n const { pipeline } = await import('@huggingface/transformers');\n this.pipeline = (await pipeline('feature-extraction', MODEL_NAME)) as unknown as FeatureExtractionPipeline;\n\n log.info(`Embedding model loaded in ${(performance.now() - start).toFixed(0)}ms`);\n }\n\n /** Embed a single text. Returns Float32Array of length 384. */\n async embed(text: string): Promise<Float32Array> {\n await this.init();\n\n const result = await this.pipeline!(text, {\n pooling: 'mean',\n normalize: true,\n });\n\n return new Float32Array(result.data);\n }\n\n /** Cosine similarity between two normalized vectors. Returns value in [-1, 1]. */\n static cosineSimilarity(a: Float32Array, b: Float32Array): number {\n let dot = 0;\n for (let i = 0; i < a.length; i++) {\n dot += a[i] * b[i];\n }\n return dot;\n }\n\n /** Embed multiple texts in one call (more efficient than calling embed() in a loop). */\n async embedBatch(texts: string[]): Promise<Float32Array[]> {\n if (texts.length === 0) return [];\n await this.init();\n\n const results: Float32Array[] = [];\n // Process one at a time to avoid memory issues with large batches\n for (const text of texts) {\n const result = await this.pipeline!(text, {\n pooling: 'mean',\n normalize: true,\n });\n results.push(new Float32Array(result.data));\n }\n\n return results;\n }\n}\n","/**\n * RoomMemory — high-level persistent memory for a room.\n *\n * Stores all conversation turns, provides semantic search,\n * and generates session summaries on session end.\n *\n * Uses SQLite + sqlite-vec for storage and local embeddings\n * via @huggingface/transformers. Everything runs in-process,\n * no external services needed.\n */\n\nimport { randomUUID } from 'crypto';\nimport { MemoryStore, type TurnRow, type SearchResult, type SessionSearchResult } from './memory-store';\nimport { Embedder } from './embedder';\nimport type { LLMPlugin, Message } from '../core/types';\nimport { createLogger } from '../utils/logger';\n\nconst log = createLogger('RoomMemory');\n\n/** Pending turn waiting to be embedded and stored. */\ninterface PendingTurn {\n speaker: string;\n text: string;\n isAgent: boolean;\n}\n\nexport interface RoomMemoryConfig {\n /** Path to SQLite database file */\n dbPath: string;\n /** Room name (scopes all data) */\n room: string;\n /** Flush pending turns every N ms (default: 5000) */\n flushIntervalMs?: number;\n}\n\nexport class RoomMemory {\n private readonly store: MemoryStore;\n private readonly embedder: Embedder;\n private readonly room: string;\n private sessionId: string | null = null;\n private participants = new Set<string>();\n private pendingTurns: PendingTurn[] = [];\n private flushTimer: ReturnType<typeof setInterval> | null = null;\n private readonly flushIntervalMs: number;\n private flushing = false;\n\n constructor(config: RoomMemoryConfig) {\n this.store = new MemoryStore(config.dbPath);\n this.embedder = new Embedder();\n this.room = config.room;\n this.flushIntervalMs = config.flushIntervalMs ?? 5000;\n }\n\n /** Get the embedder instance (for reuse in other components). */\n getEmbedder(): Embedder {\n return this.embedder;\n }\n\n /** Initialize embedder (loads model). Call once at startup. */\n async init(): Promise<void> {\n await this.embedder.init();\n }\n\n /** Start a new session for this room. */\n startSession(): string {\n this.sessionId = randomUUID();\n this.participants.clear();\n this.store.insertSession(this.sessionId, this.room);\n\n // Start periodic flush of pending turns\n this.flushTimer = setInterval(() => {\n this.flushPending().catch((err) => {\n log.error('Error flushing pending turns:', err);\n });\n }, this.flushIntervalMs);\n\n log.info(`Session started: ${this.sessionId}`);\n return this.sessionId;\n }\n\n /** Track a participant joining. */\n addParticipant(identity: string): void {\n this.participants.add(identity);\n }\n\n /**\n * Store a turn to memory. Non-blocking — queues for batch embedding.\n * Call this for EVERY final transcription, even if agent doesn't respond.\n */\n storeTurn(speaker: string, text: string, isAgent: boolean): void {\n if (!this.sessionId) {\n log.warn('storeTurn called without active session');\n return;\n }\n\n this.pendingTurns.push({ speaker, text, isAgent });\n\n // Flush immediately if we have 5+ pending turns\n if (this.pendingTurns.length >= 5) {\n this.flushPending().catch((err) => {\n log.error('Error flushing pending turns:', err);\n });\n }\n }\n\n /** Flush pending turns: embed and insert into database. */\n private async flushPending(): Promise<void> {\n if (this.flushing || this.pendingTurns.length === 0 || !this.sessionId) return;\n this.flushing = true;\n\n const batch = this.pendingTurns.splice(0);\n const texts = batch.map((t) => `[${t.speaker}]: ${t.text}`);\n\n try {\n const embeddings = await this.embedder.embedBatch(texts);\n\n for (let i = 0; i < batch.length; i++) {\n const turn = batch[i];\n this.store.insertTurn(\n this.room,\n this.sessionId,\n turn.speaker,\n turn.text,\n turn.isAgent,\n embeddings[i],\n );\n }\n\n log.debug(`Flushed ${batch.length} turns to memory`);\n } catch (err) {\n log.error('Error embedding/storing turns:', err);\n // Put turns back for retry\n this.pendingTurns.unshift(...batch);\n } finally {\n this.flushing = false;\n }\n }\n\n /**\n * Search memory for context relevant to a query.\n * Returns formatted string ready to inject into LLM system prompt.\n */\n async searchRelevant(query: string, turnLimit = 5, sessionLimit = 2): Promise<string> {\n const queryEmbedding = await this.embedder.embed(query);\n\n const turns = this.store.searchTurns(this.room, queryEmbedding, turnLimit);\n const sessions = this.store.searchSessions(this.room, queryEmbedding, sessionLimit);\n\n if (turns.length === 0 && sessions.length === 0) {\n return '';\n }\n\n const parts: string[] = [];\n\n if (sessions.length > 0) {\n parts.push('Past session summaries:');\n for (const s of sessions) {\n const date = new Date(s.started_at).toLocaleDateString();\n parts.push(` [${date}]: ${s.summary}`);\n }\n }\n\n if (turns.length > 0) {\n parts.push('Relevant past turns:');\n for (const t of turns) {\n const date = new Date(t.created_at).toLocaleDateString();\n const time = new Date(t.created_at).toLocaleTimeString([], { hour: '2-digit', minute: '2-digit' });\n parts.push(` [${date} ${time}, ${t.speaker}]: ${t.text}`);\n }\n }\n\n return parts.join('\\n');\n }\n\n /**\n * End the current session. Generates an LLM summary and stores it.\n */\n async endSession(llm: LLMPlugin): Promise<void> {\n if (!this.sessionId) return;\n\n // Flush any remaining pending turns\n await this.flushPending();\n\n // Stop flush timer\n if (this.flushTimer) {\n clearInterval(this.flushTimer);\n this.flushTimer = null;\n }\n\n const turnCount = this.store.getSessionTurnCount(this.sessionId);\n const participantList = Array.from(this.participants);\n\n if (turnCount < 3) {\n // Too few turns for meaningful summary\n this.store.endSession(this.sessionId, turnCount, participantList);\n log.info(`Session ended (${turnCount} turns, no summary)`);\n this.sessionId = null;\n return;\n }\n\n // Generate summary\n try {\n const turns = this.store.getSessionTurns(this.sessionId);\n const transcript = turns\n .map((t) => `[${t.speaker}]: ${t.text}`)\n .join('\\n');\n\n const messages: Message[] = [\n {\n role: 'system',\n content: 'Summarize this tutoring session concisely. Include: topics covered, phrases practiced, mistakes the student made, what they struggled with, and what they did well. Be factual and brief.',\n },\n { role: 'user', content: transcript },\n ];\n\n let summary = '';\n for await (const chunk of llm.chat(messages)) {\n if (chunk.type === 'token' && chunk.token) {\n summary += chunk.token;\n }\n }\n\n if (summary.trim()) {\n const embedding = await this.embedder.embed(summary.trim());\n this.store.updateSessionSummary(\n this.sessionId,\n summary.trim(),\n turnCount,\n participantList,\n embedding,\n );\n log.info(`Session ended with summary (${turnCount} turns, ${participantList.length} participants)`);\n } else {\n this.store.endSession(this.sessionId, turnCount, participantList);\n log.info(`Session ended (${turnCount} turns, summary was empty)`);\n }\n } catch (err) {\n log.error('Error generating session summary:', err);\n this.store.endSession(this.sessionId, turnCount, participantList);\n }\n\n this.sessionId = null;\n }\n\n /** Close the memory store. Flush pending turns first. */\n async close(): Promise<void> {\n if (this.flushTimer) {\n clearInterval(this.flushTimer);\n this.flushTimer = null;\n }\n\n await this.flushPending();\n this.store.close();\n }\n}\n","/**\n * @dtelecom/agents — AI voice agent framework for dTelecom rooms.\n *\n * Quick start:\n * ```ts\n * import { VoiceAgent } from '@dtelecom/agents';\n * import { DeepgramSTT, OpenRouterLLM, CartesiaTTS } from '@dtelecom/agents/providers';\n *\n * const agent = new VoiceAgent({\n * stt: new DeepgramSTT({ apiKey: '...' }),\n * llm: new OpenRouterLLM({ apiKey: '...', model: 'openai/gpt-4o' }),\n * tts: new CartesiaTTS({ apiKey: '...', voiceId: '...' }),\n * instructions: 'You are a helpful assistant.',\n * });\n *\n * await agent.start({\n * room: 'my-room',\n * apiKey: process.env.DTELECOM_API_KEY!,\n * apiSecret: process.env.DTELECOM_API_SECRET!,\n * });\n * ```\n *\n * Providers are imported from `@dtelecom/agents/providers` to keep the\n * core bundle lean. Unused providers are never loaded.\n */\n\n// Core\nexport { VoiceAgent } from './core/voice-agent';\nexport { Pipeline } from './core/pipeline';\nexport { ContextManager } from './core/context-manager';\nexport type { ContextManagerOptions } from './core/context-manager';\nexport { SentenceSplitter } from './core/sentence-splitter';\nexport { TurnDetector } from './core/turn-detector';\nexport type { TurnDetectorOptions } from './core/turn-detector';\nexport { BargeIn } from './core/barge-in';\nexport { BaseSTTStream } from './core/base-stt-stream';\n\n// Room\nexport { RoomConnection } from './room/room-connection';\nexport type { RoomConnectionOptions } from './room/room-connection';\nexport { AudioInput } from './room/audio-input';\nexport { AudioOutput } from './room/audio-output';\n\n// Types\nexport type {\n STTPlugin,\n STTStream,\n STTStreamOptions,\n TranscriptionResult,\n LLMPlugin,\n LLMChunk,\n Message,\n TTSPlugin,\n RespondMode,\n AgentConfig,\n AgentStartOptions,\n AgentState,\n AgentEvents,\n MemoryConfig,\n PipelineOptions,\n PipelineEvents,\n DataMessageHandler,\n} from './core/types';\n\n// Memory (optional — requires better-sqlite3, sqlite-vec, @huggingface/transformers)\nexport { Embedder } from './memory/embedder';\nexport { MemoryStore } from './memory/memory-store';\nexport type { TurnRow, SessionRow, SearchResult, SessionSearchResult } from './memory/memory-store';\nexport { RoomMemory } from './memory/room-memory';\nexport type { RoomMemoryConfig } from './memory/room-memory';\n\n// Utils\nexport { createLogger, setLogLevel, getLogLevel } from './utils/logger';\nexport type { LogLevel, Logger } from './utils/logger';\n","/**\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 /** 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: 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 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\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 });\n\n // Forward pipeline events\n this.pipeline.on('transcription', (result) => this.emit('transcription', result));\n this.pipeline.on('sentence', (text) => this.emit('sentence', text));\n this.pipeline.on('response', (text) => this.emit('response', text));\n this.pipeline.on('agentState', (state) => this.emit('agentState', state));\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\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 apiKey: string;\n apiSecret: string;\n identity?: string;\n name?: 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, apiKey, apiSecret, identity = 'agent', name } = options;\n\n log.info(`Connecting to room \"${roomName}\" as \"${identity}\"...`);\n\n // Dynamic import to avoid bundling server-sdk-js in the main chunk\n const { AccessToken } = await import('@dtelecom/server-sdk-js');\n\n // Create token\n const token = new AccessToken(apiKey, 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 // Discover SFU\n const wsUrl = await token.getWsUrl();\n const jwt = token.toJwt();\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 (16kHz mono, 20ms frames = 320 samples) */\nconst SAMPLE_RATE = 16000;\nconst CHANNELS = 1;\nconst FRAME_DURATION_MS = 20;\nconst SAMPLES_PER_FRAME = (SAMPLE_RATE * FRAME_DURATION_MS) / 1000; // 320 at 16kHz\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 /** 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 }\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 startSilence(): void {\n if (this.silenceInterval) return;\n\n log.debug('Starting silence keepalive (sparse, 3s interval)');\n\n this.silenceInterval = setInterval(() => {\n if (!this._playing && !this._responding && !this._stopped) {\n const f = new AudioFrame(SILENCE, SAMPLE_RATE, CHANNELS, SAMPLES_PER_FRAME);\n this.source.captureFrame(f).catch(() => {});\n }\n }, 3000);\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} 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\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\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.context = new ContextManager({\n instructions: options.instructions,\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\n if (this.llm.warmup) {\n this.llm.warmup(options.instructions).catch((err: unknown) => {\n log.warn('LLM warmup failed:', err);\n });\n }\n\n // Warm up TTS\n if (this.tts?.warmup) {\n this.tts.warmup().catch((err: unknown) => {\n log.warn('TTS warmup failed:', err);\n });\n }\n }\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 log.info('Pipeline stopped');\n }\n\n getContextManager(): ContextManager {\n return this.context;\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 // Non-empty interim → user is speaking\n if (!result.isFinal && result.text.trim()) {\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\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 // ── Producer: consume LLM stream, split into sentences ──\n const producer = async () => {\n let isFirstToken = true;\n let isFirstSentence = true;\n\n const llmStream = this.llm.chat(messages, signal);\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 === 'token' && chunk.token) {\n if (isFirstToken) {\n tLlmFirstToken = performance.now();\n isFirstToken = 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 if (signal.aborted) break;\n if (isFirstSentence) {\n tFirstSentence = performance.now();\n isFirstSentence = false;\n log.info(`first_sentence: ${(tFirstSentence - tSpeechEnd).toFixed(0)}ms — \"${sentence.slice(0, 60)}\"`);\n }\n sentenceQueue.push(sentence);\n wake();\n }\n }\n }\n } finally {\n await llmStream.return(undefined);\n }\n\n // Flush remaining text from splitter\n if (!signal.aborted) {\n const remaining = this.splitter.flush();\n if (remaining) {\n if (isFirstSentence) {\n tFirstSentence = performance.now();\n isFirstSentence = false;\n log.info(`first_sentence (flush): ${(tFirstSentence - tSpeechEnd).toFixed(0)}ms — \"${remaining.slice(0, 60)}\"`);\n }\n sentenceQueue.push(remaining);\n wake();\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 const consumer = async () => {\n this.audioOutput.beginResponse();\n try {\n while (true) {\n if (signal.aborted) break;\n\n if (sentenceQueue.length > 0) {\n const sentence = sentenceQueue.shift()!;\n // Skip sentences with no word characters (e.g. stray quotes/punctuation)\n if (!/\\w/.test(sentence)) {\n log.debug(`Skipping non-word sentence: \"${sentence}\"`);\n continue;\n }\n await this.synthesizeAndPlay(sentence, signal, (t) => {\n if (!tFirstAudioPlayed) {\n tFirstAudioPlayed = t;\n this.setAgentState('speaking');\n }\n this.emit('sentence', sentence);\n });\n continue;\n }\n\n if (producerDone) break;\n\n // Wait for producer to push a sentence\n await new Promise<void>((resolve) => {\n wakeConsumer = resolve;\n });\n wakeConsumer = null;\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', 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 the student speaks, the greeting 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 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', 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', 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 ): 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 = 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","import { EventEmitter } from 'events';\nimport type { STTStream, TranscriptionResult } from './types';\n\n/**\n * Abstract base class for STT streams.\n * Provides typed EventEmitter interface for transcription events.\n * Provider implementations should extend this class.\n */\nexport abstract class BaseSTTStream extends EventEmitter implements STTStream {\n abstract sendAudio(pcm16: Buffer): void;\n abstract close(): Promise<void>;\n\n override on(event: 'transcription', cb: (result: TranscriptionResult) => void): this;\n override on(event: 'error', cb: (error: Error) => void): this;\n // eslint-disable-next-line @typescript-eslint/no-explicit-any\n override on(event: string, cb: (...args: any[]) => void): this {\n return super.on(event, cb);\n }\n\n override emit(event: 'transcription', result: TranscriptionResult): boolean;\n override emit(event: 'error', error: Error): boolean;\n // eslint-disable-next-line @typescript-eslint/no-explicit-any\n override emit(event: string | symbol, ...args: any[]): boolean {\n return super.emit(event, ...args);\n }\n}\n"],"mappings":"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","names":["log","Database","log","log","import_events","log","import_server_sdk_node","log","log","log","log","log","sleep","log","RoomMemory","import_events"]}
|
|
1
|
+
{"version":3,"sources":["../src/utils/logger.ts","../src/memory/memory-store.ts","../src/memory/embedder.ts","../src/memory/room-memory.ts","../src/index.ts","../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","../src/core/base-stt-stream.ts"],"sourcesContent":["export type LogLevel = 'debug' | 'info' | 'warn' | 'error' | 'silent';\n\nconst LEVELS: Record<LogLevel, number> = {\n debug: 0,\n info: 1,\n warn: 2,\n error: 3,\n silent: 4,\n};\n\n/** Default to 'debug' if DEBUG env var matches our namespace */\nfunction detectLevel(): LogLevel {\n const debug = typeof process !== 'undefined' && process.env?.DEBUG;\n if (debug && (debug === '*' || debug.includes('@dtelecom/agents'))) {\n return 'debug';\n }\n return 'info';\n}\n\nlet globalLevel: LogLevel = detectLevel();\n\nexport function setLogLevel(level: LogLevel): void {\n globalLevel = level;\n}\n\nexport function getLogLevel(): LogLevel {\n return globalLevel;\n}\n\nexport interface Logger {\n debug(...args: unknown[]): void;\n info(...args: unknown[]): void;\n warn(...args: unknown[]): void;\n error(...args: unknown[]): void;\n}\n\nfunction timestamp(): string {\n const d = new Date();\n const h = String(d.getHours()).padStart(2, '0');\n const m = String(d.getMinutes()).padStart(2, '0');\n const s = String(d.getSeconds()).padStart(2, '0');\n const ms = String(d.getMilliseconds()).padStart(3, '0');\n return `${h}:${m}:${s}.${ms}`;\n}\n\nexport function createLogger(tag: string): Logger {\n const prefix = `[@dtelecom/agents:${tag}]`;\n return {\n debug(...args: unknown[]) {\n if (LEVELS[globalLevel] <= LEVELS.debug) console.debug(timestamp(), prefix, ...args);\n },\n info(...args: unknown[]) {\n if (LEVELS[globalLevel] <= LEVELS.info) console.info(timestamp(), prefix, ...args);\n },\n warn(...args: unknown[]) {\n if (LEVELS[globalLevel] <= LEVELS.warn) console.warn(timestamp(), prefix, ...args);\n },\n error(...args: unknown[]) {\n if (LEVELS[globalLevel] <= LEVELS.error) console.error(timestamp(), prefix, ...args);\n },\n };\n}\n","/**\n * MemoryStore — SQLite + sqlite-vec database layer for room memory.\n *\n * Single .db file stores:\n * - turns: every spoken turn (full transcript)\n * - sessions: meeting metadata + LLM-generated summaries\n * - turn_vectors: embedding index for semantic turn search\n * - session_vectors: embedding index for session summary search\n */\n\nimport Database from 'better-sqlite3';\nimport * as sqliteVec from 'sqlite-vec';\nimport { createLogger } from '../utils/logger';\n\nconst log = createLogger('MemoryStore');\n\nexport interface TurnRow {\n id: number;\n room: string;\n session_id: string;\n speaker: string;\n text: string;\n is_agent: number;\n created_at: number;\n}\n\nexport interface SessionRow {\n id: string;\n room: string;\n started_at: number;\n ended_at: number | null;\n participants: string | null;\n summary: string | null;\n turn_count: number;\n}\n\nexport interface SearchResult {\n speaker: string;\n text: string;\n created_at: number;\n session_id: string;\n distance: number;\n}\n\nexport interface SessionSearchResult {\n session_id: string;\n summary: string;\n started_at: number;\n distance: number;\n}\n\nexport class MemoryStore {\n private db: Database.Database;\n\n constructor(dbPath: string) {\n this.db = new Database(dbPath);\n this.db.pragma('journal_mode = WAL');\n this.db.pragma('synchronous = NORMAL');\n\n // Load sqlite-vec extension\n sqliteVec.load(this.db);\n\n this.createTables();\n log.info(`Memory store opened: ${dbPath}`);\n }\n\n private createTables(): void {\n this.db.exec(`\n CREATE TABLE IF NOT EXISTS turns (\n id INTEGER PRIMARY KEY AUTOINCREMENT,\n room TEXT NOT NULL,\n session_id TEXT NOT NULL,\n speaker TEXT NOT NULL,\n text TEXT NOT NULL,\n is_agent BOOLEAN DEFAULT 0,\n created_at INTEGER NOT NULL\n );\n\n CREATE TABLE IF NOT EXISTS sessions (\n id TEXT PRIMARY KEY,\n room TEXT NOT NULL,\n started_at INTEGER NOT NULL,\n ended_at INTEGER,\n participants TEXT,\n summary TEXT,\n turn_count INTEGER DEFAULT 0\n );\n\n CREATE INDEX IF NOT EXISTS idx_turns_room_session ON turns(room, session_id);\n CREATE INDEX IF NOT EXISTS idx_turns_room_time ON turns(room, created_at);\n CREATE INDEX IF NOT EXISTS idx_sessions_room ON sessions(room);\n `);\n\n // Vector tables — sqlite-vec virtual tables\n // Check if they exist first (CREATE VIRTUAL TABLE doesn't support IF NOT EXISTS)\n const hasVecTable = this.db.prepare(\n \"SELECT name FROM sqlite_master WHERE type='table' AND name=?\",\n );\n\n if (!hasVecTable.get('turn_vectors')) {\n this.db.exec(`\n CREATE VIRTUAL TABLE turn_vectors USING vec0(\n turn_id INTEGER PRIMARY KEY,\n embedding FLOAT[384] distance_metric=cosine\n );\n `);\n }\n\n if (!hasVecTable.get('session_vectors')) {\n this.db.exec(`\n CREATE VIRTUAL TABLE session_vectors USING vec0(\n session_id TEXT PRIMARY KEY,\n embedding FLOAT[384] distance_metric=cosine\n );\n `);\n }\n }\n\n /** Insert a turn and its embedding vector. */\n insertTurn(\n room: string,\n sessionId: string,\n speaker: string,\n text: string,\n isAgent: boolean,\n embedding: Float32Array,\n ): number {\n const stmt = this.db.prepare(`\n INSERT INTO turns (room, session_id, speaker, text, is_agent, created_at)\n VALUES (?, ?, ?, ?, ?, ?)\n `);\n const info = stmt.run(room, sessionId, speaker, text, isAgent ? 1 : 0, Date.now());\n const turnId = info.lastInsertRowid;\n\n // Insert embedding vector — sqlite-vec requires BigInt for integer PKs\n this.db.prepare(\n 'INSERT INTO turn_vectors (turn_id, embedding) VALUES (?, ?)',\n ).run(BigInt(turnId), Buffer.from(embedding.buffer));\n\n return Number(turnId);\n }\n\n /** Create a new session record. */\n insertSession(id: string, room: string): void {\n this.db.prepare(`\n INSERT INTO sessions (id, room, started_at)\n VALUES (?, ?, ?)\n `).run(id, room, Date.now());\n }\n\n /** Update a session with summary and end time. */\n updateSessionSummary(\n sessionId: string,\n summary: string,\n turnCount: number,\n participants: string[],\n embedding: Float32Array,\n ): void {\n this.db.prepare(`\n UPDATE sessions\n SET summary = ?, ended_at = ?, turn_count = ?, participants = ?\n WHERE id = ?\n `).run(summary, Date.now(), turnCount, JSON.stringify(participants), sessionId);\n\n // Insert summary embedding\n this.db.prepare(\n 'INSERT INTO session_vectors (session_id, embedding) VALUES (?, ?)',\n ).run(sessionId, Buffer.from(embedding.buffer));\n }\n\n /** End a session without summary (e.g., too few turns). */\n endSession(sessionId: string, turnCount: number, participants: string[]): void {\n this.db.prepare(`\n UPDATE sessions\n SET ended_at = ?, turn_count = ?, participants = ?\n WHERE id = ?\n `).run(Date.now(), turnCount, JSON.stringify(participants), sessionId);\n }\n\n /** KNN search turns by embedding similarity. */\n searchTurns(room: string, queryEmbedding: Float32Array, limit: number): SearchResult[] {\n const rows = this.db.prepare(`\n SELECT t.speaker, t.text, t.created_at, t.session_id, tv.distance\n FROM turn_vectors tv\n JOIN turns t ON t.id = tv.turn_id\n WHERE t.room = ?\n AND tv.embedding MATCH ?\n AND k = ?\n ORDER BY tv.distance\n `).all(room, Buffer.from(queryEmbedding.buffer), limit * 2) as (TurnRow & { distance: number })[];\n\n // sqlite-vec returns k results from the vector index, then we filter by room\n return rows.slice(0, limit).map((r) => ({\n speaker: r.speaker,\n text: r.text,\n created_at: r.created_at,\n session_id: r.session_id,\n distance: r.distance,\n }));\n }\n\n /** KNN search session summaries by embedding similarity. */\n searchSessions(room: string, queryEmbedding: Float32Array, limit: number): SessionSearchResult[] {\n const rows = this.db.prepare(`\n SELECT s.id as session_id, s.summary, s.started_at, sv.distance\n FROM session_vectors sv\n JOIN sessions s ON s.id = sv.session_id\n WHERE s.room = ?\n AND sv.embedding MATCH ?\n AND k = ?\n ORDER BY sv.distance\n `).all(room, Buffer.from(queryEmbedding.buffer), limit * 2) as SessionSearchResult[];\n\n return rows\n .filter((r) => r.summary)\n .slice(0, limit);\n }\n\n /** Get the last N turns from a specific session. */\n getRecentTurns(room: string, sessionId: string, limit: number): TurnRow[] {\n return this.db.prepare(`\n SELECT * FROM turns\n WHERE room = ? AND session_id = ?\n ORDER BY created_at DESC\n LIMIT ?\n `).all(room, sessionId, limit) as TurnRow[];\n }\n\n /** Get all turns for a session (for summarization). */\n getSessionTurns(sessionId: string): TurnRow[] {\n return this.db.prepare(`\n SELECT * FROM turns\n WHERE session_id = ?\n ORDER BY created_at ASC\n `).all(sessionId) as TurnRow[];\n }\n\n /** Get total turn count for a session. */\n getSessionTurnCount(sessionId: string): number {\n const row = this.db.prepare(\n 'SELECT COUNT(*) as count FROM turns WHERE session_id = ?',\n ).get(sessionId) as { count: number };\n return row.count;\n }\n\n /** Close the database. */\n close(): void {\n this.db.close();\n log.info('Memory store closed');\n }\n}\n","/**\n * Embedder — local text embedding via @huggingface/transformers.\n *\n * Uses Xenova/all-MiniLM-L6-v2 (384 dimensions, ~22MB model).\n * Runs entirely in-process — no API calls, no cost.\n */\n\nimport { createLogger } from '../utils/logger';\n\nconst log = createLogger('Embedder');\n\nconst MODEL_NAME = 'Xenova/all-MiniLM-L6-v2';\nconst EMBEDDING_DIM = 384;\n\ntype FeatureExtractionPipeline = (\n text: string | string[],\n options?: { pooling?: string; normalize?: boolean },\n) => Promise<{ data: Float32Array }>;\n\nexport class Embedder {\n private pipeline: FeatureExtractionPipeline | null = null;\n private initPromise: Promise<void> | null = null;\n\n get dimensions(): number {\n return EMBEDDING_DIM;\n }\n\n /** Load the embedding model. Call once at startup. */\n async init(): Promise<void> {\n if (this.pipeline) return;\n if (this.initPromise) return this.initPromise;\n\n this.initPromise = this.loadModel();\n return this.initPromise;\n }\n\n private async loadModel(): Promise<void> {\n const start = performance.now();\n log.info(`Loading embedding model \"${MODEL_NAME}\"...`);\n\n const { pipeline } = await import('@huggingface/transformers');\n this.pipeline = (await pipeline('feature-extraction', MODEL_NAME)) as unknown as FeatureExtractionPipeline;\n\n log.info(`Embedding model loaded in ${(performance.now() - start).toFixed(0)}ms`);\n }\n\n /** Embed a single text. Returns Float32Array of length 384. */\n async embed(text: string): Promise<Float32Array> {\n await this.init();\n\n const result = await this.pipeline!(text, {\n pooling: 'mean',\n normalize: true,\n });\n\n return new Float32Array(result.data);\n }\n\n /** Cosine similarity between two normalized vectors. Returns value in [-1, 1]. */\n static cosineSimilarity(a: Float32Array, b: Float32Array): number {\n let dot = 0;\n for (let i = 0; i < a.length; i++) {\n dot += a[i] * b[i];\n }\n return dot;\n }\n\n /** Embed multiple texts in one call (more efficient than calling embed() in a loop). */\n async embedBatch(texts: string[]): Promise<Float32Array[]> {\n if (texts.length === 0) return [];\n await this.init();\n\n const results: Float32Array[] = [];\n // Process one at a time to avoid memory issues with large batches\n for (const text of texts) {\n const result = await this.pipeline!(text, {\n pooling: 'mean',\n normalize: true,\n });\n results.push(new Float32Array(result.data));\n }\n\n return results;\n }\n}\n","/**\n * RoomMemory — high-level persistent memory for a room.\n *\n * Stores all conversation turns, provides semantic search,\n * and generates session summaries on session end.\n *\n * Uses SQLite + sqlite-vec for storage and local embeddings\n * via @huggingface/transformers. Everything runs in-process,\n * no external services needed.\n */\n\nimport { randomUUID } from 'crypto';\nimport { MemoryStore, type TurnRow, type SearchResult, type SessionSearchResult } from './memory-store';\nimport { Embedder } from './embedder';\nimport type { LLMPlugin, Message } from '../core/types';\nimport { createLogger } from '../utils/logger';\n\nconst log = createLogger('RoomMemory');\n\n/** Pending turn waiting to be embedded and stored. */\ninterface PendingTurn {\n speaker: string;\n text: string;\n isAgent: boolean;\n}\n\nexport interface RoomMemoryConfig {\n /** Path to SQLite database file */\n dbPath: string;\n /** Room name (scopes all data) */\n room: string;\n /** Flush pending turns every N ms (default: 5000) */\n flushIntervalMs?: number;\n}\n\nexport class RoomMemory {\n private readonly store: MemoryStore;\n private readonly embedder: Embedder;\n private readonly room: string;\n private sessionId: string | null = null;\n private participants = new Set<string>();\n private pendingTurns: PendingTurn[] = [];\n private flushTimer: ReturnType<typeof setInterval> | null = null;\n private readonly flushIntervalMs: number;\n private flushing = false;\n\n constructor(config: RoomMemoryConfig) {\n this.store = new MemoryStore(config.dbPath);\n this.embedder = new Embedder();\n this.room = config.room;\n this.flushIntervalMs = config.flushIntervalMs ?? 5000;\n }\n\n /** Get the embedder instance (for reuse in other components). */\n getEmbedder(): Embedder {\n return this.embedder;\n }\n\n /** Initialize embedder (loads model). Call once at startup. */\n async init(): Promise<void> {\n await this.embedder.init();\n }\n\n /** Start a new session for this room. */\n startSession(): string {\n this.sessionId = randomUUID();\n this.participants.clear();\n this.store.insertSession(this.sessionId, this.room);\n\n // Start periodic flush of pending turns\n this.flushTimer = setInterval(() => {\n this.flushPending().catch((err) => {\n log.error('Error flushing pending turns:', err);\n });\n }, this.flushIntervalMs);\n\n log.info(`Session started: ${this.sessionId}`);\n return this.sessionId;\n }\n\n /** Track a participant joining. */\n addParticipant(identity: string): void {\n this.participants.add(identity);\n }\n\n /**\n * Store a turn to memory. Non-blocking — queues for batch embedding.\n * Call this for EVERY final transcription, even if agent doesn't respond.\n */\n storeTurn(speaker: string, text: string, isAgent: boolean): void {\n if (!this.sessionId) {\n log.warn('storeTurn called without active session');\n return;\n }\n\n this.pendingTurns.push({ speaker, text, isAgent });\n\n // Flush immediately if we have 5+ pending turns\n if (this.pendingTurns.length >= 5) {\n this.flushPending().catch((err) => {\n log.error('Error flushing pending turns:', err);\n });\n }\n }\n\n /** Flush pending turns: embed and insert into database. */\n private async flushPending(): Promise<void> {\n if (this.flushing || this.pendingTurns.length === 0 || !this.sessionId) return;\n this.flushing = true;\n\n const batch = this.pendingTurns.splice(0);\n const texts = batch.map((t) => `[${t.speaker}]: ${t.text}`);\n\n try {\n const embeddings = await this.embedder.embedBatch(texts);\n\n for (let i = 0; i < batch.length; i++) {\n const turn = batch[i];\n this.store.insertTurn(\n this.room,\n this.sessionId,\n turn.speaker,\n turn.text,\n turn.isAgent,\n embeddings[i],\n );\n }\n\n log.debug(`Flushed ${batch.length} turns to memory`);\n } catch (err) {\n log.error('Error embedding/storing turns:', err);\n // Put turns back for retry\n this.pendingTurns.unshift(...batch);\n } finally {\n this.flushing = false;\n }\n }\n\n /**\n * Search memory for context relevant to a query.\n * Returns formatted string ready to inject into LLM system prompt.\n */\n async searchRelevant(query: string, turnLimit = 5, sessionLimit = 2): Promise<string> {\n const queryEmbedding = await this.embedder.embed(query);\n\n const turns = this.store.searchTurns(this.room, queryEmbedding, turnLimit);\n const sessions = this.store.searchSessions(this.room, queryEmbedding, sessionLimit);\n\n if (turns.length === 0 && sessions.length === 0) {\n return '';\n }\n\n const parts: string[] = [];\n\n if (sessions.length > 0) {\n parts.push('Past session summaries:');\n for (const s of sessions) {\n const date = new Date(s.started_at).toLocaleDateString();\n parts.push(` [${date}]: ${s.summary}`);\n }\n }\n\n if (turns.length > 0) {\n parts.push('Relevant past turns:');\n for (const t of turns) {\n const date = new Date(t.created_at).toLocaleDateString();\n const time = new Date(t.created_at).toLocaleTimeString([], { hour: '2-digit', minute: '2-digit' });\n parts.push(` [${date} ${time}, ${t.speaker}]: ${t.text}`);\n }\n }\n\n return parts.join('\\n');\n }\n\n /**\n * End the current session. Generates an LLM summary and stores it.\n */\n async endSession(llm: LLMPlugin): Promise<void> {\n if (!this.sessionId) return;\n\n // Flush any remaining pending turns\n await this.flushPending();\n\n // Stop flush timer\n if (this.flushTimer) {\n clearInterval(this.flushTimer);\n this.flushTimer = null;\n }\n\n const turnCount = this.store.getSessionTurnCount(this.sessionId);\n const participantList = Array.from(this.participants);\n\n if (turnCount < 3) {\n // Too few turns for meaningful summary\n this.store.endSession(this.sessionId, turnCount, participantList);\n log.info(`Session ended (${turnCount} turns, no summary)`);\n this.sessionId = null;\n return;\n }\n\n // Generate summary\n try {\n const turns = this.store.getSessionTurns(this.sessionId);\n const transcript = turns\n .map((t) => `[${t.speaker}]: ${t.text}`)\n .join('\\n');\n\n const messages: Message[] = [\n {\n role: 'system',\n content: 'Summarize this tutoring session concisely. Include: topics covered, phrases practiced, mistakes the student made, what they struggled with, and what they did well. Be factual and brief.',\n },\n { role: 'user', content: transcript },\n ];\n\n let summary = '';\n for await (const chunk of llm.chat(messages)) {\n if (chunk.type === 'token' && chunk.token) {\n summary += chunk.token;\n }\n }\n\n if (summary.trim()) {\n const embedding = await this.embedder.embed(summary.trim());\n this.store.updateSessionSummary(\n this.sessionId,\n summary.trim(),\n turnCount,\n participantList,\n embedding,\n );\n log.info(`Session ended with summary (${turnCount} turns, ${participantList.length} participants)`);\n } else {\n this.store.endSession(this.sessionId, turnCount, participantList);\n log.info(`Session ended (${turnCount} turns, summary was empty)`);\n }\n } catch (err) {\n log.error('Error generating session summary:', err);\n this.store.endSession(this.sessionId, turnCount, participantList);\n }\n\n this.sessionId = null;\n }\n\n /** Close the memory store. Flush pending turns first. */\n async close(): Promise<void> {\n if (this.flushTimer) {\n clearInterval(this.flushTimer);\n this.flushTimer = null;\n }\n\n await this.flushPending();\n this.store.close();\n }\n}\n","/**\n * @dtelecom/agents — AI voice agent framework for dTelecom rooms.\n *\n * Quick start:\n * ```ts\n * import { VoiceAgent } from '@dtelecom/agents';\n * import { DeepgramSTT, OpenRouterLLM, CartesiaTTS } from '@dtelecom/agents/providers';\n *\n * const agent = new VoiceAgent({\n * stt: new DeepgramSTT({ apiKey: '...' }),\n * llm: new OpenRouterLLM({ apiKey: '...', model: 'openai/gpt-4o' }),\n * tts: new CartesiaTTS({ apiKey: '...', voiceId: '...' }),\n * instructions: 'You are a helpful assistant.',\n * });\n *\n * await agent.start({\n * room: 'my-room',\n * apiKey: process.env.DTELECOM_API_KEY!,\n * apiSecret: process.env.DTELECOM_API_SECRET!,\n * });\n * ```\n *\n * Providers are imported from `@dtelecom/agents/providers` to keep the\n * core bundle lean. Unused providers are never loaded.\n */\n\n// Core\nexport { VoiceAgent } from './core/voice-agent';\nexport { Pipeline } from './core/pipeline';\nexport { ContextManager } from './core/context-manager';\nexport type { ContextManagerOptions } from './core/context-manager';\nexport { SentenceSplitter } from './core/sentence-splitter';\nexport { TurnDetector } from './core/turn-detector';\nexport type { TurnDetectorOptions } from './core/turn-detector';\nexport { BargeIn } from './core/barge-in';\nexport { BaseSTTStream } from './core/base-stt-stream';\n\n// Room\nexport { RoomConnection } from './room/room-connection';\nexport type { RoomConnectionOptions } from './room/room-connection';\nexport { AudioInput } from './room/audio-input';\nexport { AudioOutput } from './room/audio-output';\n\n// Types\nexport type {\n STTPlugin,\n STTStream,\n STTStreamOptions,\n TranscriptionResult,\n LLMPlugin,\n LLMChunk,\n Message,\n TTSPlugin,\n RespondMode,\n AgentConfig,\n AgentStartOptions,\n AgentState,\n AgentEvents,\n MemoryConfig,\n PipelineOptions,\n PipelineEvents,\n DataMessageHandler,\n} from './core/types';\n\n// Memory is a separate entry point: import from '@dtelecom/agents-js/memory'\n// Requires optional peer deps: better-sqlite3, sqlite-vec, @huggingface/transformers\n\n// Utils\nexport { createLogger, setLogLevel, getLogLevel } from './utils/logger';\nexport type { LogLevel, Logger } from './utils/logger';\n","/**\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 /** 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: 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 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\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 });\n\n // Forward pipeline events\n this.pipeline.on('transcription', (result) => this.emit('transcription', result));\n this.pipeline.on('sentence', (text) => this.emit('sentence', text));\n this.pipeline.on('response', (text) => this.emit('response', text));\n this.pipeline.on('agentState', (state) => this.emit('agentState', state));\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\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 apiKey: string;\n apiSecret: string;\n identity?: string;\n name?: 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, apiKey, apiSecret, identity = 'agent', name } = options;\n\n log.info(`Connecting to room \"${roomName}\" as \"${identity}\"...`);\n\n // Dynamic import to avoid bundling server-sdk-js in the main chunk\n const { AccessToken } = await import('@dtelecom/server-sdk-js');\n\n // Create token\n const token = new AccessToken(apiKey, 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 // Discover SFU\n const wsUrl = await token.getWsUrl();\n const jwt = token.toJwt();\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 /** 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 }\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 startSilence(): void {\n if (this.silenceInterval) return;\n\n log.debug('Starting silence keepalive (sparse, 3s interval)');\n\n this.silenceInterval = setInterval(() => {\n if (!this._playing && !this._responding && !this._stopped) {\n const f = new AudioFrame(SILENCE, SAMPLE_RATE, CHANNELS, SAMPLES_PER_FRAME);\n this.source.captureFrame(f).catch(() => {});\n }\n }, 3000);\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} 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\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\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.context = new ContextManager({\n instructions: options.instructions,\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\n if (this.llm.warmup) {\n this.llm.warmup(options.instructions).catch((err: unknown) => {\n log.warn('LLM warmup failed:', err);\n });\n }\n\n // Warm up TTS\n if (this.tts?.warmup) {\n this.tts.warmup().catch((err: unknown) => {\n log.warn('TTS warmup failed:', err);\n });\n }\n }\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 log.info('Pipeline stopped');\n }\n\n getContextManager(): ContextManager {\n return this.context;\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 // Non-empty interim → user is speaking\n if (!result.isFinal && result.text.trim()) {\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\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 // ── Producer: consume LLM stream, split into sentences ──\n const producer = async () => {\n let isFirstToken = true;\n let isFirstSentence = true;\n\n const llmStream = this.llm.chat(messages, signal);\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 === 'token' && chunk.token) {\n if (isFirstToken) {\n tLlmFirstToken = performance.now();\n isFirstToken = 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 if (signal.aborted) break;\n if (isFirstSentence) {\n tFirstSentence = performance.now();\n isFirstSentence = false;\n log.info(`first_sentence: ${(tFirstSentence - tSpeechEnd).toFixed(0)}ms — \"${sentence.slice(0, 60)}\"`);\n }\n sentenceQueue.push(sentence);\n wake();\n }\n }\n }\n } finally {\n await llmStream.return(undefined);\n }\n\n // Flush remaining text from splitter\n if (!signal.aborted) {\n const remaining = this.splitter.flush();\n if (remaining) {\n if (isFirstSentence) {\n tFirstSentence = performance.now();\n isFirstSentence = false;\n log.info(`first_sentence (flush): ${(tFirstSentence - tSpeechEnd).toFixed(0)}ms — \"${remaining.slice(0, 60)}\"`);\n }\n sentenceQueue.push(remaining);\n wake();\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 const consumer = async () => {\n this.audioOutput.beginResponse();\n try {\n while (true) {\n if (signal.aborted) break;\n\n if (sentenceQueue.length > 0) {\n const sentence = sentenceQueue.shift()!;\n // Skip sentences with no word characters (e.g. stray quotes/punctuation)\n if (!/\\w/.test(sentence)) {\n log.debug(`Skipping non-word sentence: \"${sentence}\"`);\n continue;\n }\n await this.synthesizeAndPlay(sentence, signal, (t) => {\n if (!tFirstAudioPlayed) {\n tFirstAudioPlayed = t;\n this.setAgentState('speaking');\n }\n this.emit('sentence', sentence);\n });\n continue;\n }\n\n if (producerDone) break;\n\n // Wait for producer to push a sentence\n await new Promise<void>((resolve) => {\n wakeConsumer = resolve;\n });\n wakeConsumer = null;\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', 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 the student speaks, the greeting 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 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', 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', 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 ): 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 = 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","import { EventEmitter } from 'events';\nimport type { STTStream, TranscriptionResult } from './types';\n\n/**\n * Abstract base class for STT streams.\n * Provides typed EventEmitter interface for transcription events.\n * Provider implementations should extend this class.\n */\nexport abstract class BaseSTTStream extends EventEmitter implements STTStream {\n abstract sendAudio(pcm16: Buffer): void;\n abstract close(): Promise<void>;\n\n override on(event: 'transcription', cb: (result: TranscriptionResult) => void): this;\n override on(event: 'error', cb: (error: Error) => void): this;\n // eslint-disable-next-line @typescript-eslint/no-explicit-any\n override on(event: string, cb: (...args: any[]) => void): this {\n return super.on(event, cb);\n }\n\n override emit(event: 'transcription', result: TranscriptionResult): boolean;\n override emit(event: 'error', error: Error): boolean;\n // eslint-disable-next-line @typescript-eslint/no-explicit-any\n override emit(event: string | symbol, ...args: any[]): boolean {\n return super.emit(event, ...args);\n }\n}\n"],"mappings":"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","names":["log","Database","log","log","import_events","log","import_server_sdk_node","log","log","log","log","log","sleep","log","RoomMemory","import_events"]}
|
package/dist/index.mjs
CHANGED
|
@@ -1,11 +1,6 @@
|
|
|
1
1
|
import {
|
|
2
2
|
BaseSTTStream
|
|
3
3
|
} from "./chunk-6OWWB2X7.mjs";
|
|
4
|
-
import {
|
|
5
|
-
Embedder,
|
|
6
|
-
MemoryStore,
|
|
7
|
-
RoomMemory
|
|
8
|
-
} from "./chunk-RQKGHAFV.mjs";
|
|
9
4
|
import {
|
|
10
5
|
createLogger,
|
|
11
6
|
getLogLevel,
|
|
@@ -141,7 +136,7 @@ import { AudioFrame } from "@dtelecom/server-sdk-node";
|
|
|
141
136
|
import { writeFileSync, appendFileSync, existsSync, mkdirSync } from "fs";
|
|
142
137
|
import { join } from "path";
|
|
143
138
|
var log3 = createLogger("AudioOutput");
|
|
144
|
-
var SAMPLE_RATE =
|
|
139
|
+
var SAMPLE_RATE = 48e3;
|
|
145
140
|
var CHANNELS = 1;
|
|
146
141
|
var FRAME_DURATION_MS = 20;
|
|
147
142
|
var SAMPLES_PER_FRAME = SAMPLE_RATE * FRAME_DURATION_MS / 1e3;
|
|
@@ -1029,8 +1024,8 @@ var VoiceAgent = class extends EventEmitter2 {
|
|
|
1029
1024
|
}
|
|
1030
1025
|
log8.info(`Starting agent for room "${options.room}"...`);
|
|
1031
1026
|
if (this.config.memory?.enabled) {
|
|
1032
|
-
const { RoomMemory
|
|
1033
|
-
this.memory = new
|
|
1027
|
+
const { RoomMemory } = await import("./room-memory-VAREPHY6.mjs");
|
|
1028
|
+
this.memory = new RoomMemory({
|
|
1034
1029
|
dbPath: this.config.memory.dbPath ?? "./data/memory.db",
|
|
1035
1030
|
room: options.room
|
|
1036
1031
|
});
|
|
@@ -1186,11 +1181,8 @@ export {
|
|
|
1186
1181
|
BargeIn,
|
|
1187
1182
|
BaseSTTStream,
|
|
1188
1183
|
ContextManager,
|
|
1189
|
-
Embedder,
|
|
1190
|
-
MemoryStore,
|
|
1191
1184
|
Pipeline,
|
|
1192
1185
|
RoomConnection,
|
|
1193
|
-
RoomMemory,
|
|
1194
1186
|
SentenceSplitter,
|
|
1195
1187
|
TurnDetector,
|
|
1196
1188
|
VoiceAgent,
|