@dtelecom/agents-js 0.1.15 → 0.1.17
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/{chunk-QGPL42WP.mjs → chunk-PDVPYJKU.mjs} +2 -2
- package/dist/chunk-PDVPYJKU.mjs.map +1 -0
- package/dist/index.d.mts +14 -2
- package/dist/index.d.ts +14 -2
- package/dist/index.js +80 -54
- package/dist/index.js.map +1 -1
- package/dist/index.mjs +80 -54
- package/dist/index.mjs.map +1 -1
- package/dist/memory/index.d.mts +1 -1
- package/dist/memory/index.d.ts +1 -1
- package/dist/memory/index.js +1 -1
- package/dist/memory/index.js.map +1 -1
- package/dist/memory/index.mjs +1 -1
- package/dist/providers/index.d.mts +5 -5
- package/dist/providers/index.d.ts +5 -5
- package/dist/providers/index.js +95 -49
- package/dist/providers/index.js.map +1 -1
- package/dist/providers/index.mjs +95 -49
- package/dist/providers/index.mjs.map +1 -1
- package/dist/room-memory-HROLCL7G.mjs +8 -0
- package/dist/{types-MPHcuMhp.d.mts → types-AWgTLEQ-.d.mts} +6 -2
- package/dist/{types-MPHcuMhp.d.ts → types-AWgTLEQ-.d.ts} +6 -2
- package/package.json +1 -2
- package/dist/chunk-QGPL42WP.mjs.map +0 -1
- package/dist/room-memory-JVZQLL64.mjs +0 -8
- /package/dist/{room-memory-JVZQLL64.mjs.map → room-memory-HROLCL7G.mjs.map} +0 -0
package/dist/memory/index.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"sources":["../../src/memory/index.ts","../../src/utils/logger.ts","../../src/memory/embedder.ts","../../src/memory/memory-store.ts","../../src/memory/room-memory.ts"],"sourcesContent":["export { Embedder } from './embedder';\nexport { MemoryStore } from './memory-store';\nexport type { TurnRow, SessionRow, SearchResult, SessionSearchResult } from './memory-store';\nexport { RoomMemory } from './room-memory';\nexport type { RoomMemoryConfig } from './room-memory';\n","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 * 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 * 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 * 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 conversation concisely. Include: key topics discussed, decisions made, and important details. 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"],"mappings":"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","names":["log","Database","log"]}
|
|
1
|
+
{"version":3,"sources":["../../src/memory/index.ts","../../src/utils/logger.ts","../../src/memory/embedder.ts","../../src/memory/memory-store.ts","../../src/memory/room-memory.ts"],"sourcesContent":["export { Embedder } from './embedder';\nexport { MemoryStore } from './memory-store';\nexport type { TurnRow, SessionRow, SearchResult, SessionSearchResult } from './memory-store';\nexport { RoomMemory } from './room-memory';\nexport type { RoomMemoryConfig } from './room-memory';\n","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 * 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 * 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 * 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 conversation concisely. Include: key topics discussed, decisions made, and important details. Be factual and brief.',\n },\n { role: 'user', content: transcript },\n ];\n\n let summary = '';\n for await (const chunk of llm.chat(messages, undefined, { plainText: true })) {\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"],"mappings":"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","names":["log","Database","log"]}
|
package/dist/memory/index.mjs
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import {
|
|
1
|
+
import { i as STTPlugin, j as STTStreamOptions, S as STTStream, L as LLMPlugin, M as Message, e as LLMChatOptions, f as LLMChunk, k as TTSPlugin } from '../types-AWgTLEQ-.mjs';
|
|
2
2
|
import '@dtelecom/server-sdk-node';
|
|
3
3
|
|
|
4
4
|
/**
|
|
@@ -91,7 +91,7 @@ declare class OpenRouterLLM implements LLMPlugin {
|
|
|
91
91
|
* Primes the HTTP/TLS connection and model loading on the provider side.
|
|
92
92
|
*/
|
|
93
93
|
warmup(systemPrompt: string): Promise<void>;
|
|
94
|
-
chat(messages: Message[], signal?: AbortSignal): AsyncGenerator<LLMChunk>;
|
|
94
|
+
chat(messages: Message[], signal?: AbortSignal, options?: LLMChatOptions): AsyncGenerator<LLMChunk>;
|
|
95
95
|
}
|
|
96
96
|
|
|
97
97
|
/**
|
|
@@ -235,14 +235,14 @@ declare class DtelecomSTT implements STTPlugin {
|
|
|
235
235
|
* - Connect to ws://<server>:<port> (address from options, no API key)
|
|
236
236
|
* - Send config: {"config":{"voice":"af_heart","lang_code":"a","speed":1.0}}
|
|
237
237
|
* - Send text: {"text":"Hello world"} — uses config defaults
|
|
238
|
-
* - Send text with per-message override: {"text":"Hola","voice":"
|
|
239
|
-
* - Receive: {"type":"generating","text":"..."} then binary PCM16
|
|
238
|
+
* - Send text with per-message override: {"text":"Hola","voice":"ef_dora","lang_code":"e","speed":1.0}
|
|
239
|
+
* - Receive: {"type":"generating","text":"..."} then binary PCM16 48kHz chunks, then {"type":"done"}
|
|
240
240
|
* - Cancel: {"type":"clear"} → {"type":"cleared"}
|
|
241
241
|
*
|
|
242
242
|
* Key differences from DeepgramTTS:
|
|
243
243
|
* - Single WebSocket connection (not per-language pool)
|
|
244
244
|
* - Per-message voice/language switching instead of separate connections
|
|
245
|
-
* - Server outputs
|
|
245
|
+
* - Server outputs 48kHz PCM16 (resampled from Kokoro's native 24kHz)
|
|
246
246
|
* - Uses SSML <lang> tags to route text segments to correct voice (same as DeepgramTTS)
|
|
247
247
|
*/
|
|
248
248
|
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import {
|
|
1
|
+
import { i as STTPlugin, j as STTStreamOptions, S as STTStream, L as LLMPlugin, M as Message, e as LLMChatOptions, f as LLMChunk, k as TTSPlugin } from '../types-AWgTLEQ-.js';
|
|
2
2
|
import '@dtelecom/server-sdk-node';
|
|
3
3
|
|
|
4
4
|
/**
|
|
@@ -91,7 +91,7 @@ declare class OpenRouterLLM implements LLMPlugin {
|
|
|
91
91
|
* Primes the HTTP/TLS connection and model loading on the provider side.
|
|
92
92
|
*/
|
|
93
93
|
warmup(systemPrompt: string): Promise<void>;
|
|
94
|
-
chat(messages: Message[], signal?: AbortSignal): AsyncGenerator<LLMChunk>;
|
|
94
|
+
chat(messages: Message[], signal?: AbortSignal, options?: LLMChatOptions): AsyncGenerator<LLMChunk>;
|
|
95
95
|
}
|
|
96
96
|
|
|
97
97
|
/**
|
|
@@ -235,14 +235,14 @@ declare class DtelecomSTT implements STTPlugin {
|
|
|
235
235
|
* - Connect to ws://<server>:<port> (address from options, no API key)
|
|
236
236
|
* - Send config: {"config":{"voice":"af_heart","lang_code":"a","speed":1.0}}
|
|
237
237
|
* - Send text: {"text":"Hello world"} — uses config defaults
|
|
238
|
-
* - Send text with per-message override: {"text":"Hola","voice":"
|
|
239
|
-
* - Receive: {"type":"generating","text":"..."} then binary PCM16
|
|
238
|
+
* - Send text with per-message override: {"text":"Hola","voice":"ef_dora","lang_code":"e","speed":1.0}
|
|
239
|
+
* - Receive: {"type":"generating","text":"..."} then binary PCM16 48kHz chunks, then {"type":"done"}
|
|
240
240
|
* - Cancel: {"type":"clear"} → {"type":"cleared"}
|
|
241
241
|
*
|
|
242
242
|
* Key differences from DeepgramTTS:
|
|
243
243
|
* - Single WebSocket connection (not per-language pool)
|
|
244
244
|
* - Per-message voice/language switching instead of separate connections
|
|
245
|
-
* - Server outputs
|
|
245
|
+
* - Server outputs 48kHz PCM16 (resampled from Kokoro's native 24kHz)
|
|
246
246
|
* - Uses SSML <lang> tags to route text segments to correct voice (same as DeepgramTTS)
|
|
247
247
|
*/
|
|
248
248
|
|
package/dist/providers/index.js
CHANGED
|
@@ -348,7 +348,7 @@ var OpenRouterLLM = class {
|
|
|
348
348
|
log2.warn("LLM warmup failed (non-fatal):", err);
|
|
349
349
|
}
|
|
350
350
|
}
|
|
351
|
-
async *chat(messages, signal) {
|
|
351
|
+
async *chat(messages, signal, options) {
|
|
352
352
|
const body = {
|
|
353
353
|
model: this.model,
|
|
354
354
|
messages,
|
|
@@ -359,7 +359,7 @@ var OpenRouterLLM = class {
|
|
|
359
359
|
if (this.provider) {
|
|
360
360
|
body.provider = { ...this.provider };
|
|
361
361
|
}
|
|
362
|
-
if (this.responseFormat) {
|
|
362
|
+
if (this.responseFormat && !options?.plainText) {
|
|
363
363
|
body.response_format = this.responseFormat;
|
|
364
364
|
const prov = body.provider ?? {};
|
|
365
365
|
prov.require_parameters = true;
|
|
@@ -385,10 +385,65 @@ var OpenRouterLLM = class {
|
|
|
385
385
|
const reader = response.body.getReader();
|
|
386
386
|
const decoder = new TextDecoder();
|
|
387
387
|
let buffer = "";
|
|
388
|
-
const structured = !!this.responseFormat;
|
|
388
|
+
const structured = !!this.responseFormat && !options?.plainText;
|
|
389
389
|
let jsonBuffer = "";
|
|
390
|
-
let
|
|
391
|
-
|
|
390
|
+
let segmentsYielded = false;
|
|
391
|
+
let lastUsage;
|
|
392
|
+
let inSegmentsArray = false;
|
|
393
|
+
let objectStart = -1;
|
|
394
|
+
let braceDepth = 0;
|
|
395
|
+
let scanIndex = 0;
|
|
396
|
+
let inString = false;
|
|
397
|
+
let escaped = false;
|
|
398
|
+
function extractSegments(buf) {
|
|
399
|
+
const results = [];
|
|
400
|
+
for (let i = scanIndex; i < buf.length; i++) {
|
|
401
|
+
const ch = buf[i];
|
|
402
|
+
if (escaped) {
|
|
403
|
+
escaped = false;
|
|
404
|
+
continue;
|
|
405
|
+
}
|
|
406
|
+
if (ch === "\\" && inString) {
|
|
407
|
+
escaped = true;
|
|
408
|
+
continue;
|
|
409
|
+
}
|
|
410
|
+
if (ch === '"') {
|
|
411
|
+
inString = !inString;
|
|
412
|
+
continue;
|
|
413
|
+
}
|
|
414
|
+
if (inString) continue;
|
|
415
|
+
if (!inSegmentsArray) {
|
|
416
|
+
if (ch === "[") {
|
|
417
|
+
const before = buf.slice(0, i).trimEnd();
|
|
418
|
+
if (before.endsWith(":") && buf.slice(0, i).includes('"segments"')) {
|
|
419
|
+
inSegmentsArray = true;
|
|
420
|
+
}
|
|
421
|
+
}
|
|
422
|
+
continue;
|
|
423
|
+
}
|
|
424
|
+
if (ch === "{") {
|
|
425
|
+
if (braceDepth === 0) objectStart = i;
|
|
426
|
+
braceDepth++;
|
|
427
|
+
} else if (ch === "}") {
|
|
428
|
+
braceDepth--;
|
|
429
|
+
if (braceDepth === 0 && objectStart >= 0) {
|
|
430
|
+
const objStr = buf.slice(objectStart, i + 1);
|
|
431
|
+
try {
|
|
432
|
+
const seg = JSON.parse(objStr);
|
|
433
|
+
if (seg.lang && seg.text) {
|
|
434
|
+
results.push({ lang: seg.lang, text: seg.text });
|
|
435
|
+
}
|
|
436
|
+
} catch {
|
|
437
|
+
}
|
|
438
|
+
objectStart = -1;
|
|
439
|
+
}
|
|
440
|
+
} else if (ch === "]" && braceDepth === 0) {
|
|
441
|
+
inSegmentsArray = false;
|
|
442
|
+
}
|
|
443
|
+
}
|
|
444
|
+
scanIndex = buf.length;
|
|
445
|
+
return results;
|
|
446
|
+
}
|
|
392
447
|
try {
|
|
393
448
|
while (true) {
|
|
394
449
|
if (signal?.aborted) break;
|
|
@@ -401,10 +456,7 @@ var OpenRouterLLM = class {
|
|
|
401
456
|
const trimmed = line.trim();
|
|
402
457
|
if (!trimmed || !trimmed.startsWith("data: ")) continue;
|
|
403
458
|
const data = trimmed.slice(6);
|
|
404
|
-
if (data === "[DONE]")
|
|
405
|
-
yield { type: "done" };
|
|
406
|
-
return;
|
|
407
|
-
}
|
|
459
|
+
if (data === "[DONE]") break;
|
|
408
460
|
try {
|
|
409
461
|
const parsed = JSON.parse(data);
|
|
410
462
|
const choice = parsed.choices?.[0];
|
|
@@ -413,31 +465,20 @@ var OpenRouterLLM = class {
|
|
|
413
465
|
if (delta?.content) {
|
|
414
466
|
if (structured) {
|
|
415
467
|
jsonBuffer += delta.content;
|
|
416
|
-
|
|
417
|
-
|
|
418
|
-
|
|
419
|
-
|
|
420
|
-
const text = match[2].replace(/\\(.)/g, (_, c) => {
|
|
421
|
-
if (c === "n") return "\n";
|
|
422
|
-
if (c === "t") return " ";
|
|
423
|
-
return c;
|
|
424
|
-
});
|
|
425
|
-
lastSegmentIndex = segmentRe.lastIndex;
|
|
426
|
-
yield { type: "segment", segment: { lang, text } };
|
|
468
|
+
const segments = extractSegments(jsonBuffer);
|
|
469
|
+
for (const seg of segments) {
|
|
470
|
+
yield { type: "segment", segment: seg };
|
|
471
|
+
segmentsYielded = true;
|
|
427
472
|
}
|
|
428
473
|
} else {
|
|
429
474
|
yield { type: "token", token: delta.content };
|
|
430
475
|
}
|
|
431
476
|
}
|
|
432
477
|
if (parsed.usage) {
|
|
433
|
-
|
|
434
|
-
|
|
435
|
-
|
|
436
|
-
promptTokens: parsed.usage.prompt_tokens,
|
|
437
|
-
completionTokens: parsed.usage.completion_tokens
|
|
438
|
-
}
|
|
478
|
+
lastUsage = {
|
|
479
|
+
promptTokens: parsed.usage.prompt_tokens,
|
|
480
|
+
completionTokens: parsed.usage.completion_tokens
|
|
439
481
|
};
|
|
440
|
-
return;
|
|
441
482
|
}
|
|
442
483
|
} catch {
|
|
443
484
|
}
|
|
@@ -446,10 +487,10 @@ var OpenRouterLLM = class {
|
|
|
446
487
|
} finally {
|
|
447
488
|
reader.releaseLock();
|
|
448
489
|
}
|
|
449
|
-
if (structured &&
|
|
450
|
-
log2.warn(`
|
|
490
|
+
if (structured && !segmentsYielded && jsonBuffer.length > 0) {
|
|
491
|
+
log2.warn(`LLM returned no segments. Raw JSON: "${jsonBuffer.slice(0, 300)}"`);
|
|
451
492
|
}
|
|
452
|
-
yield { type: "done" };
|
|
493
|
+
yield { type: "done", ...lastUsage ? { usage: lastUsage } : {} };
|
|
453
494
|
}
|
|
454
495
|
};
|
|
455
496
|
|
|
@@ -772,13 +813,21 @@ var DeepgramTTS = class {
|
|
|
772
813
|
}
|
|
773
814
|
async *synthesize(text, signal) {
|
|
774
815
|
if (signal?.aborted) return;
|
|
775
|
-
const
|
|
816
|
+
const rawSegments = this.multiLanguage ? parseLangSegments(text, this.defaultLang) : [{ lang: this.defaultLang, text }];
|
|
817
|
+
const segments = [];
|
|
818
|
+
for (const seg of rawSegments) {
|
|
819
|
+
if (!seg.text.trim()) continue;
|
|
820
|
+
if (!/\p{L}/u.test(seg.text) && segments.length > 0) {
|
|
821
|
+
segments[segments.length - 1].text += seg.text;
|
|
822
|
+
} else {
|
|
823
|
+
segments.push(seg);
|
|
824
|
+
}
|
|
825
|
+
}
|
|
776
826
|
const silenceBytes = Math.round(this.sampleRate * 0.2) * 2;
|
|
777
827
|
const silence = Buffer.alloc(silenceBytes);
|
|
778
828
|
let prevLang = null;
|
|
779
829
|
for (const segment of segments) {
|
|
780
830
|
if (signal?.aborted) break;
|
|
781
|
-
if (!segment.text.trim()) continue;
|
|
782
831
|
const lang = this.models[segment.lang] ? segment.lang : this.defaultLang;
|
|
783
832
|
if (prevLang !== null && lang !== prevLang) {
|
|
784
833
|
yield silence;
|
|
@@ -1024,7 +1073,8 @@ var DtelecomSTTStream = class extends BaseSTTStream {
|
|
|
1024
1073
|
this.handleVadEvent(msg);
|
|
1025
1074
|
} else if (type === "pong") {
|
|
1026
1075
|
} else if (type === "error") {
|
|
1027
|
-
const
|
|
1076
|
+
const errData = msg.error;
|
|
1077
|
+
const errorMsg = msg.message || (typeof errData === "string" ? errData : JSON.stringify(errData)) || "Unknown STT error";
|
|
1028
1078
|
log5.error(`dTelecom STT error: ${errorMsg}`);
|
|
1029
1079
|
this.emit("error", new Error(errorMsg));
|
|
1030
1080
|
}
|
|
@@ -1086,18 +1136,7 @@ var DtelecomSTTStream = class extends BaseSTTStream {
|
|
|
1086
1136
|
|
|
1087
1137
|
// src/providers/dtelecom-tts.ts
|
|
1088
1138
|
var import_ws5 = __toESM(require("ws"));
|
|
1089
|
-
var import_wave_resampler = require("wave-resampler");
|
|
1090
1139
|
var log6 = createLogger("DtelecomTTS");
|
|
1091
|
-
function resample24to48(input) {
|
|
1092
|
-
const samples = new Int16Array(input.buffer, input.byteOffset, input.length / 2);
|
|
1093
|
-
if (samples.length === 0) return Buffer.alloc(0);
|
|
1094
|
-
const resampled = (0, import_wave_resampler.resample)(samples, 24e3, 48e3, { method: "sinc", LPF: false });
|
|
1095
|
-
const output = new Int16Array(resampled.length);
|
|
1096
|
-
for (let i = 0; i < resampled.length; i++) {
|
|
1097
|
-
output[i] = Math.round(resampled[i]);
|
|
1098
|
-
}
|
|
1099
|
-
return Buffer.from(output.buffer, output.byteOffset, output.byteLength);
|
|
1100
|
-
}
|
|
1101
1140
|
var DtelecomTTS = class {
|
|
1102
1141
|
serverUrl;
|
|
1103
1142
|
voices;
|
|
@@ -1149,13 +1188,21 @@ var DtelecomTTS = class {
|
|
|
1149
1188
|
}
|
|
1150
1189
|
async *synthesize(text, signal) {
|
|
1151
1190
|
if (signal?.aborted) return;
|
|
1152
|
-
const
|
|
1191
|
+
const rawSegments = parseLangSegments(text, this.defaultLang);
|
|
1192
|
+
const segments = [];
|
|
1193
|
+
for (const seg of rawSegments) {
|
|
1194
|
+
if (!seg.text.trim()) continue;
|
|
1195
|
+
if (!/\p{L}/u.test(seg.text) && segments.length > 0) {
|
|
1196
|
+
segments[segments.length - 1].text += seg.text;
|
|
1197
|
+
} else {
|
|
1198
|
+
segments.push(seg);
|
|
1199
|
+
}
|
|
1200
|
+
}
|
|
1153
1201
|
const silenceBytes = Math.round(48e3 * 0.2) * 2;
|
|
1154
1202
|
const silence = Buffer.alloc(silenceBytes);
|
|
1155
1203
|
let prevLang = null;
|
|
1156
1204
|
for (const segment of segments) {
|
|
1157
1205
|
if (signal?.aborted) break;
|
|
1158
|
-
if (!segment.text.trim()) continue;
|
|
1159
1206
|
const lang = this.voices[segment.lang] ? segment.lang : this.defaultLang;
|
|
1160
1207
|
if (prevLang !== null && lang !== prevLang) {
|
|
1161
1208
|
yield silence;
|
|
@@ -1165,7 +1212,6 @@ var DtelecomTTS = class {
|
|
|
1165
1212
|
}
|
|
1166
1213
|
}
|
|
1167
1214
|
async *synthesizeSegment(lang, text, signal) {
|
|
1168
|
-
log6.debug(`Synthesizing [${lang}]: "${text.slice(0, 60)}"`);
|
|
1169
1215
|
await this.ensureConnection();
|
|
1170
1216
|
const ws = this.ws;
|
|
1171
1217
|
if (!ws || ws.readyState !== import_ws5.default.OPEN) {
|
|
@@ -1191,6 +1237,7 @@ var DtelecomTTS = class {
|
|
|
1191
1237
|
msg.lang_code = voiceConfig.langCode;
|
|
1192
1238
|
msg.speed = this.speed;
|
|
1193
1239
|
}
|
|
1240
|
+
log6.info(`TTS send [${lang}]: voice=${voiceConfig?.voice ?? "default"} lang_code=${voiceConfig?.langCode ?? "default"} "${text.slice(0, 60)}"`);
|
|
1194
1241
|
ws.send(JSON.stringify(msg));
|
|
1195
1242
|
try {
|
|
1196
1243
|
while (true) {
|
|
@@ -1244,8 +1291,7 @@ var DtelecomTTS = class {
|
|
|
1244
1291
|
if (!state) return;
|
|
1245
1292
|
if (isBinary) {
|
|
1246
1293
|
const buf = Buffer.isBuffer(data) ? data : Buffer.from(data);
|
|
1247
|
-
|
|
1248
|
-
state.chunks.push(resampled);
|
|
1294
|
+
state.chunks.push(buf);
|
|
1249
1295
|
state.wake?.();
|
|
1250
1296
|
} else {
|
|
1251
1297
|
try {
|