UNPKG

contextual-agent-sdk

Version:

SDK for building AI agents with seamless voice-text context switching

245 lines 9.14 kB
"use strict"; Object.defineProperty(exports, "__esModule", { value: true }); exports.SessionStateManager = void 0; const StorageFactory_1 = require("../storage/StorageFactory"); class SessionStateManager { storage; constructor(storageConfig) { this.storage = storageConfig ? StorageFactory_1.StorageFactory.createProvider(storageConfig) : StorageFactory_1.StorageFactory.createFromEnv(); } async createSession(sessionId, userId) { const existingSession = await this.storage.getSession(sessionId); if (existingSession) { return existingSession; } const session = { sessionId, userId, startTime: new Date(), lastActivity: new Date(), currentModality: 'text', totalMessages: 0, context: this.createEmptyContext(), metadata: this.createEmptyMetadata() }; await this.storage.createSession(sessionId, session); return session; } async bridgeContextForModality(sessionId, targetModality) { const session = await this.storage.getSession(sessionId); if (!session) { throw new Error(`Session ${sessionId} not found`); } const currentModality = session.currentModality; if (currentModality === targetModality) { return this.getRecentContext(session, 3); } let bridgedContext = ''; if (currentModality === 'voice' && targetModality === 'text') { bridgedContext = this.bridgeVoiceToText(session); } else if (currentModality === 'text' && targetModality === 'voice') { bridgedContext = this.bridgeTextToVoice(session); } this.addFlowState(session, 'context_bridged', targetModality, { from: currentModality, to: targetModality, bridgeType: `${currentModality}_to_${targetModality}` }); await this.storage.updateSession(sessionId, session); return bridgedContext; } async getSession(sessionId) { return this.storage.getSession(sessionId); } async updateSession(sessionId, message, modality) { const session = await this.storage.getSession(sessionId); if (!session) { throw new Error(`Session ${sessionId} not found`); } if (session.currentModality !== modality) { session.metadata.modalitySwitches++; this.addFlowState(session, 'modality_switch', modality, { from: session.currentModality, to: modality }); } session.currentModality = modality; session.totalMessages++; session.lastActivity = new Date(); this.addToMemoryBank(session, message); this.updateContextFromMessage(session, message); await this.storage.updateSession(sessionId, session); return session; } async getConversationSummary(sessionId) { const session = await this.storage.getSession(sessionId); if (!session) return ''; const { context } = session; const recentMemories = context.memoryBank .sort((a, b) => b.timestamp.getTime() - a.timestamp.getTime()) .slice(0, 5); let summary = ''; if (context.topic) { summary += `Topic: ${context.topic}\n`; } if (recentMemories.length > 0) { summary += `Recent conversation:\n`; recentMemories.forEach((memory) => { summary += `- ${memory.content} (${memory.modality})\n`; }); } return summary; } async destroySession(sessionId) { return this.storage.deleteSession(sessionId); } createEmptyContext() { return { entities: [], conversationFlow: [], memoryBank: [] }; } createEmptyMetadata() { return { modalitySwitches: 0, averageResponseTime: 0 }; } addToMemoryBank(session, message) { const memory = { id: message.id, content: message.content, importance: this.calculateImportance(message), timestamp: message.timestamp, tags: this.extractTags(message.content), modality: message.modality }; session.context.memoryBank.push(memory); if (session.context.memoryBank.length > 50) { session.context.memoryBank = session.context.memoryBank .sort((a, b) => b.importance - a.importance || b.timestamp.getTime() - a.timestamp.getTime()) .slice(0, 50); } } updateContextFromMessage(session, message) { const entities = this.extractEntities(message.content); entities.forEach((entity) => { const existing = session.context.entities.find((e) => e.name === entity.name && e.type === entity.type); if (!existing) { session.context.entities.push({ ...entity, source: message.modality }); } }); if (!session.context.topic) { session.context.topic = this.detectTopic(message.content); } } addFlowState(session, step, modality, data) { const flowState = { step, modality, timestamp: new Date(), data }; session.context.conversationFlow.push(flowState); if (session.context.conversationFlow.length > 20) { session.context.conversationFlow = session.context.conversationFlow.slice(-20); } } bridgeVoiceToText(session) { const recentVoiceMemories = session.context.memoryBank .filter(m => m.modality === 'voice') .sort((a, b) => b.timestamp.getTime() - a.timestamp.getTime()) .slice(0, 3); if (recentVoiceMemories.length === 0) { return this.getRecentContext(session, 3); } let context = "Previous voice conversation context:\n"; recentVoiceMemories.forEach((memory, index) => { context += `${index + 1}. ${memory.content}\n`; }); context += "\nNow switching to text mode - please provide structured responses."; return context; } bridgeTextToVoice(session) { const recentTextMemories = session.context.memoryBank .filter(m => m.modality === 'text') .sort((a, b) => b.timestamp.getTime() - a.timestamp.getTime()) .slice(0, 3); if (recentTextMemories.length === 0) { return this.getRecentContext(session, 3); } let context = "We were just discussing: "; const topics = recentTextMemories.map(m => { return m.content.length > 100 ? m.content.substring(0, 100) + "..." : m.content; }).join(". "); context += topics; context += ". Now switching to voice conversation - please speak naturally."; return context; } getRecentContext(session, count = 5) { const recentMemories = session.context.memoryBank .sort((a, b) => b.timestamp.getTime() - a.timestamp.getTime()) .slice(0, count); return recentMemories.map(m => m.content).join("\n"); } calculateImportance(message) { let score = 0.5; if (message.content.includes('?')) score += 0.3; if (message.content.length > 100) score += 0.2; if (message.role === 'system') score -= 0.3; return Math.max(0, Math.min(1, score)); } extractTags(content) { const tags = []; if (content.includes('question') || content.includes('?')) tags.push('question'); if (content.includes('help') || content.includes('support')) tags.push('help'); return tags; } extractEntities(content) { const entities = []; const emailRegex = /\b[A-Za-z0-9._%+-]+@[A-Za-z0-9.-]+\.[A-Z|a-z]{2,}\b/g; const emails = content.match(emailRegex); if (emails) { emails.forEach(email => { entities.push({ name: email, type: 'email', value: email, confidence: 0.9, source: 'text' }); }); } return entities; } detectTopic(content) { const lowercaseContent = content.toLowerCase(); if (lowercaseContent.includes('order') || lowercaseContent.includes('shipping')) { return 'order_management'; } if (lowercaseContent.includes('account') || lowercaseContent.includes('profile')) { return 'account_management'; } return 'general'; } async shutdown() { await this.storage.shutdown(); } } exports.SessionStateManager = SessionStateManager; //# sourceMappingURL=SessionStateManager.js.map