@dtelecom/agents-js 0.1.10 → 0.1.12
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-RQKGHAFV.mjs → chunk-QGPL42WP.mjs} +2 -2
- package/dist/chunk-QGPL42WP.mjs.map +1 -0
- package/dist/index.d.mts +3 -3
- package/dist/index.d.ts +3 -3
- package/dist/index.js +54 -37
- package/dist/index.js.map +1 -1
- package/dist/index.mjs +54 -37
- 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 +14 -13
- package/dist/providers/index.d.ts +14 -13
- package/dist/providers/index.js +47 -102
- package/dist/providers/index.js.map +1 -1
- package/dist/providers/index.mjs +47 -102
- package/dist/providers/index.mjs.map +1 -1
- package/dist/room-memory-JVZQLL64.mjs +8 -0
- package/dist/{types-f6SAlHpW.d.mts → types-50-Kyruo.d.mts} +11 -3
- package/dist/{types-f6SAlHpW.d.ts → types-50-Kyruo.d.ts} +11 -3
- package/package.json +2 -3
- package/dist/chunk-RQKGHAFV.mjs.map +0 -1
- package/dist/room-memory-VAREPHY6.mjs +0 -8
- /package/dist/{room-memory-VAREPHY6.mjs.map → room-memory-JVZQLL64.mjs.map} +0 -0
|
@@ -363,7 +363,7 @@ var RoomMemory = class {
|
|
|
363
363
|
const messages = [
|
|
364
364
|
{
|
|
365
365
|
role: "system",
|
|
366
|
-
content: "Summarize this
|
|
366
|
+
content: "Summarize this conversation concisely. Include: key topics discussed, decisions made, and important details. Be factual and brief."
|
|
367
367
|
},
|
|
368
368
|
{ role: "user", content: transcript }
|
|
369
369
|
];
|
|
@@ -409,4 +409,4 @@ export {
|
|
|
409
409
|
Embedder,
|
|
410
410
|
RoomMemory
|
|
411
411
|
};
|
|
412
|
-
//# sourceMappingURL=chunk-
|
|
412
|
+
//# sourceMappingURL=chunk-QGPL42WP.mjs.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"sources":["../src/memory/room-memory.ts","../src/memory/memory-store.ts","../src/memory/embedder.ts"],"sourcesContent":["/**\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","/**\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"],"mappings":"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","names":["log","log"]}
|
package/dist/index.d.mts
CHANGED
|
@@ -1,8 +1,8 @@
|
|
|
1
1
|
import * as _dtelecom_server_sdk_node from '@dtelecom/server-sdk-node';
|
|
2
2
|
import { Room, AudioSource, RemoteAudioTrack, AudioFrame } from '@dtelecom/server-sdk-node';
|
|
3
3
|
import { EventEmitter } from 'events';
|
|
4
|
-
import { A as AgentConfig, a as AgentStartOptions, M as Message, L as LLMPlugin, P as PipelineOptions, b as AgentState, S as STTStream, T as TranscriptionResult } from './types-
|
|
5
|
-
export { c as AgentEvents, d as AudioOutput, D as DataMessageHandler, e as LLMChunk, f as MemoryConfig, g as PipelineEvents, R as RespondMode, h as STTPlugin, i as STTStreamOptions, j as TTSPlugin } from './types-
|
|
4
|
+
import { A as AgentConfig, a as AgentStartOptions, M as Message, L as LLMPlugin, P as PipelineOptions, b as AgentState, S as STTStream, T as TranscriptionResult } from './types-50-Kyruo.mjs';
|
|
5
|
+
export { c as AgentEvents, d as AudioOutput, D as DataMessageHandler, e as LLMChunk, f as MemoryConfig, g as PipelineEvents, R as RespondMode, h as STTPlugin, i as STTStreamOptions, j as TTSPlugin } from './types-50-Kyruo.mjs';
|
|
6
6
|
|
|
7
7
|
declare class VoiceAgent extends EventEmitter {
|
|
8
8
|
private readonly config;
|
|
@@ -136,7 +136,7 @@ declare class Pipeline extends EventEmitter {
|
|
|
136
136
|
private processTurn;
|
|
137
137
|
/**
|
|
138
138
|
* Speak text directly via TTS, bypassing the LLM.
|
|
139
|
-
* Supports barge-in — if
|
|
139
|
+
* Supports barge-in — if a participant speaks, the playback is cut short.
|
|
140
140
|
* Adds the text to conversation context so the LLM knows what was said.
|
|
141
141
|
*/
|
|
142
142
|
say(text: string): Promise<void>;
|
package/dist/index.d.ts
CHANGED
|
@@ -1,8 +1,8 @@
|
|
|
1
1
|
import * as _dtelecom_server_sdk_node from '@dtelecom/server-sdk-node';
|
|
2
2
|
import { Room, AudioSource, RemoteAudioTrack, AudioFrame } from '@dtelecom/server-sdk-node';
|
|
3
3
|
import { EventEmitter } from 'events';
|
|
4
|
-
import { A as AgentConfig, a as AgentStartOptions, M as Message, L as LLMPlugin, P as PipelineOptions, b as AgentState, S as STTStream, T as TranscriptionResult } from './types-
|
|
5
|
-
export { c as AgentEvents, d as AudioOutput, D as DataMessageHandler, e as LLMChunk, f as MemoryConfig, g as PipelineEvents, R as RespondMode, h as STTPlugin, i as STTStreamOptions, j as TTSPlugin } from './types-
|
|
4
|
+
import { A as AgentConfig, a as AgentStartOptions, M as Message, L as LLMPlugin, P as PipelineOptions, b as AgentState, S as STTStream, T as TranscriptionResult } from './types-50-Kyruo.js';
|
|
5
|
+
export { c as AgentEvents, d as AudioOutput, D as DataMessageHandler, e as LLMChunk, f as MemoryConfig, g as PipelineEvents, R as RespondMode, h as STTPlugin, i as STTStreamOptions, j as TTSPlugin } from './types-50-Kyruo.js';
|
|
6
6
|
|
|
7
7
|
declare class VoiceAgent extends EventEmitter {
|
|
8
8
|
private readonly config;
|
|
@@ -136,7 +136,7 @@ declare class Pipeline extends EventEmitter {
|
|
|
136
136
|
private processTurn;
|
|
137
137
|
/**
|
|
138
138
|
* Speak text directly via TTS, bypassing the LLM.
|
|
139
|
-
* Supports barge-in — if
|
|
139
|
+
* Supports barge-in — if a participant speaks, the playback is cut short.
|
|
140
140
|
* Adds the text to conversation context so the LLM knows what was said.
|
|
141
141
|
*/
|
|
142
142
|
say(text: string): Promise<void>;
|
package/dist/index.js
CHANGED
|
@@ -468,7 +468,7 @@ var init_room_memory = __esm({
|
|
|
468
468
|
const messages = [
|
|
469
469
|
{
|
|
470
470
|
role: "system",
|
|
471
|
-
content: "Summarize this
|
|
471
|
+
content: "Summarize this conversation concisely. Include: key topics discussed, decisions made, and important details. Be factual and brief."
|
|
472
472
|
},
|
|
473
473
|
{ role: "user", content: transcript }
|
|
474
474
|
];
|
|
@@ -1179,7 +1179,8 @@ var Pipeline = class extends import_events.EventEmitter {
|
|
|
1179
1179
|
this.beforeRespond = options.beforeRespond;
|
|
1180
1180
|
this.memory = options.memory;
|
|
1181
1181
|
this.context = new ContextManager({
|
|
1182
|
-
instructions: options.instructions
|
|
1182
|
+
instructions: options.instructions,
|
|
1183
|
+
maxContextTokens: options.maxContextTokens
|
|
1183
1184
|
});
|
|
1184
1185
|
this.turnDetector = new TurnDetector({
|
|
1185
1186
|
silenceTimeoutMs: options.silenceTimeoutMs
|
|
@@ -1350,32 +1351,57 @@ var Pipeline = class extends import_events.EventEmitter {
|
|
|
1350
1351
|
const wake = () => {
|
|
1351
1352
|
wakeConsumer?.();
|
|
1352
1353
|
};
|
|
1354
|
+
let isFirstSentence = true;
|
|
1355
|
+
const pushSentence = (text2) => {
|
|
1356
|
+
if (signal.aborted) return;
|
|
1357
|
+
if (isFirstSentence) {
|
|
1358
|
+
tFirstSentence = performance.now();
|
|
1359
|
+
isFirstSentence = false;
|
|
1360
|
+
log7.info(`first_sentence: ${(tFirstSentence - tSpeechEnd).toFixed(0)}ms \u2014 "${text2.slice(0, 60)}"`);
|
|
1361
|
+
}
|
|
1362
|
+
sentenceQueue.push(text2);
|
|
1363
|
+
wake();
|
|
1364
|
+
};
|
|
1353
1365
|
const producer = async () => {
|
|
1354
|
-
let
|
|
1355
|
-
|
|
1366
|
+
let isFirstChunk = true;
|
|
1367
|
+
const defaultLang = this.tts?.defaultLanguage;
|
|
1368
|
+
const segBuf = [];
|
|
1369
|
+
const flushSegments = () => {
|
|
1370
|
+
if (segBuf.length === 0) return;
|
|
1371
|
+
const combined = segBuf.map(
|
|
1372
|
+
(s) => s.lang !== defaultLang ? `<lang xml:lang="${s.lang}">${s.text}</lang>` : s.text
|
|
1373
|
+
).join(" ");
|
|
1374
|
+
segBuf.length = 0;
|
|
1375
|
+
pushSentence(combined);
|
|
1376
|
+
};
|
|
1356
1377
|
const llmStream = this.llm.chat(messages, signal);
|
|
1357
1378
|
try {
|
|
1358
1379
|
while (!signal.aborted) {
|
|
1359
1380
|
const { value: chunk, done } = await llmStream.next();
|
|
1360
1381
|
if (done || !chunk) break;
|
|
1361
1382
|
if (signal.aborted) break;
|
|
1362
|
-
if (chunk.type === "
|
|
1363
|
-
if (
|
|
1383
|
+
if (chunk.type === "segment" && chunk.segment) {
|
|
1384
|
+
if (isFirstChunk) {
|
|
1385
|
+
tLlmFirstToken = performance.now();
|
|
1386
|
+
isFirstChunk = false;
|
|
1387
|
+
log7.info(`llm_first_segment: ${(tLlmFirstToken - tSpeechEnd).toFixed(0)}ms`);
|
|
1388
|
+
}
|
|
1389
|
+
if (fullResponse) fullResponse += " ";
|
|
1390
|
+
fullResponse += chunk.segment.text;
|
|
1391
|
+
segBuf.push(chunk.segment);
|
|
1392
|
+
if (/[.!?]["'»)]*\s*$/.test(chunk.segment.text)) {
|
|
1393
|
+
flushSegments();
|
|
1394
|
+
}
|
|
1395
|
+
} else if (chunk.type === "token" && chunk.token) {
|
|
1396
|
+
if (isFirstChunk) {
|
|
1364
1397
|
tLlmFirstToken = performance.now();
|
|
1365
|
-
|
|
1398
|
+
isFirstChunk = false;
|
|
1366
1399
|
log7.info(`llm_first_token: ${(tLlmFirstToken - tSpeechEnd).toFixed(0)}ms`);
|
|
1367
1400
|
}
|
|
1368
1401
|
fullResponse += chunk.token;
|
|
1369
1402
|
const sentences = this.splitter.push(chunk.token);
|
|
1370
1403
|
for (const sentence of sentences) {
|
|
1371
|
-
|
|
1372
|
-
if (isFirstSentence) {
|
|
1373
|
-
tFirstSentence = performance.now();
|
|
1374
|
-
isFirstSentence = false;
|
|
1375
|
-
log7.info(`first_sentence: ${(tFirstSentence - tSpeechEnd).toFixed(0)}ms \u2014 "${sentence.slice(0, 60)}"`);
|
|
1376
|
-
}
|
|
1377
|
-
sentenceQueue.push(sentence);
|
|
1378
|
-
wake();
|
|
1404
|
+
pushSentence(sentence);
|
|
1379
1405
|
}
|
|
1380
1406
|
}
|
|
1381
1407
|
}
|
|
@@ -1383,15 +1409,13 @@ var Pipeline = class extends import_events.EventEmitter {
|
|
|
1383
1409
|
await llmStream.return(void 0);
|
|
1384
1410
|
}
|
|
1385
1411
|
if (!signal.aborted) {
|
|
1412
|
+
flushSegments();
|
|
1386
1413
|
const remaining = this.splitter.flush();
|
|
1387
1414
|
if (remaining) {
|
|
1388
|
-
|
|
1389
|
-
|
|
1390
|
-
|
|
1391
|
-
|
|
1392
|
-
}
|
|
1393
|
-
sentenceQueue.push(remaining);
|
|
1394
|
-
wake();
|
|
1415
|
+
pushSentence(remaining);
|
|
1416
|
+
}
|
|
1417
|
+
if (!fullResponse.trim()) {
|
|
1418
|
+
log7.warn("LLM produced no output (empty response or no segments detected)");
|
|
1395
1419
|
}
|
|
1396
1420
|
}
|
|
1397
1421
|
producerDone = true;
|
|
@@ -1408,16 +1432,12 @@ var Pipeline = class extends import_events.EventEmitter {
|
|
|
1408
1432
|
log7.debug(`Skipping non-word sentence: "${sentence}"`);
|
|
1409
1433
|
continue;
|
|
1410
1434
|
}
|
|
1411
|
-
|
|
1412
|
-
if (this.tts?.preprocessText) {
|
|
1413
|
-
processed = await this.tts.preprocessText(sentence, signal);
|
|
1414
|
-
}
|
|
1415
|
-
await this.synthesizeAndPlay(processed, signal, (t) => {
|
|
1435
|
+
await this.synthesizeAndPlay(sentence, signal, (t) => {
|
|
1416
1436
|
if (!tFirstAudioPlayed) {
|
|
1417
1437
|
tFirstAudioPlayed = t;
|
|
1418
1438
|
this.setAgentState("speaking");
|
|
1419
1439
|
}
|
|
1420
|
-
this.emit("sentence", this.cleanText(
|
|
1440
|
+
this.emit("sentence", this.cleanText(sentence));
|
|
1421
1441
|
});
|
|
1422
1442
|
continue;
|
|
1423
1443
|
}
|
|
@@ -1469,7 +1489,7 @@ var Pipeline = class extends import_events.EventEmitter {
|
|
|
1469
1489
|
}
|
|
1470
1490
|
/**
|
|
1471
1491
|
* Speak text directly via TTS, bypassing the LLM.
|
|
1472
|
-
* Supports barge-in — if
|
|
1492
|
+
* Supports barge-in — if a participant speaks, the playback is cut short.
|
|
1473
1493
|
* Adds the text to conversation context so the LLM knows what was said.
|
|
1474
1494
|
*/
|
|
1475
1495
|
async say(text) {
|
|
@@ -1484,19 +1504,15 @@ var Pipeline = class extends import_events.EventEmitter {
|
|
|
1484
1504
|
const signal = this.bargeIn.startCycle();
|
|
1485
1505
|
this.audioOutput.beginResponse();
|
|
1486
1506
|
this.setAgentState("thinking");
|
|
1487
|
-
|
|
1488
|
-
if (this.tts?.preprocessText) {
|
|
1489
|
-
processed = await this.tts.preprocessText(text, signal);
|
|
1490
|
-
}
|
|
1491
|
-
await this.synthesizeAndPlay(processed, signal, () => {
|
|
1507
|
+
await this.synthesizeAndPlay(text, signal, () => {
|
|
1492
1508
|
this.setAgentState("speaking");
|
|
1493
|
-
this.emit("sentence", this.cleanText(
|
|
1509
|
+
this.emit("sentence", this.cleanText(text));
|
|
1494
1510
|
});
|
|
1495
1511
|
if (!signal.aborted) {
|
|
1496
1512
|
await this.audioOutput.writeSilence(40);
|
|
1497
1513
|
this.context.addAgentTurn(text);
|
|
1498
1514
|
this.memory?.storeTurn("assistant", text, true);
|
|
1499
|
-
this.emit("response", this.cleanText(
|
|
1515
|
+
this.emit("response", this.cleanText(text));
|
|
1500
1516
|
}
|
|
1501
1517
|
await sleep2(AUDIO_DRAIN_MS);
|
|
1502
1518
|
this.setAgentState("idle");
|
|
@@ -1626,7 +1642,8 @@ var VoiceAgent = class extends import_events2.EventEmitter {
|
|
|
1626
1642
|
respondMode: this.config.respondMode,
|
|
1627
1643
|
agentName: this.config.agentName,
|
|
1628
1644
|
nameVariants: this.config.nameVariants,
|
|
1629
|
-
memory: this.memory ?? void 0
|
|
1645
|
+
memory: this.memory ?? void 0,
|
|
1646
|
+
maxContextTokens: this.config.maxContextTokens
|
|
1630
1647
|
});
|
|
1631
1648
|
this.pipeline.on("transcription", (result) => this.emit("transcription", result));
|
|
1632
1649
|
this.pipeline.on("sentence", (text) => this.emit("sentence", text));
|