contextual-agent-sdk
Version:
SDK for building AI agents with seamless voice-text context switching
245 lines • 9.14 kB
JavaScript
;
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