contextual-agent-sdk
Version:
SDK for building AI agents with seamless voice-text context switching
228 lines • 8.21 kB
JavaScript
"use strict";
Object.defineProperty(exports, "__esModule", { value: true });
exports.RedisStorageProvider = void 0;
const redis_1 = require("redis");
class RedisStorageProvider {
client;
prefix = 'session:';
eventHandlers = new Set();
cleanupTimer;
constructor(config) {
const clientConfig = {
url: config.url,
username: config.username,
password: config.password
};
if (config.ssl) {
clientConfig.socket = {
tls: true,
rejectUnauthorized: false
};
}
this.client = (0, redis_1.createClient)(clientConfig);
this.client.on('error', (error) => {
this.emitEvent(this.createEvent('error', error));
});
this.client.on('reconnecting', (retries) => {
if (retries > (config.retryAttempts || 3)) {
this.emitEvent(this.createEvent('error', new Error('Redis connection failed after max retries')));
}
});
if (config.cleanupInterval && config.maxAge) {
this.cleanupTimer = setInterval(() => {
this.cleanup(config.maxAge).catch(error => {
this.emitEvent(this.createEvent('error', error));
});
}, config.cleanupInterval);
}
}
async createSession(sessionId, session) {
const key = this.getKey(sessionId);
await this.client.set(key, JSON.stringify(session));
if (session.metadata?.maxAge) {
await this.client.expire(key, Math.floor(session.metadata.maxAge / 1000));
}
this.emitEvent(this.createEvent('session_created', sessionId));
}
async getSession(sessionId) {
const data = await this.client.get(this.getKey(sessionId));
if (!data)
return null;
try {
const session = JSON.parse(data);
session.startTime = new Date(session.startTime);
session.lastActivity = new Date(session.lastActivity);
session.context.memoryBank = session.context.memoryBank.map(memory => ({
...memory,
timestamp: new Date(memory.timestamp)
}));
return session;
}
catch (error) {
this.emitEvent(this.createEvent('error', error));
return null;
}
}
async updateSession(sessionId, session) {
const key = this.getKey(sessionId);
await this.client.set(key, JSON.stringify(session));
if (session.metadata?.maxAge) {
await this.client.expire(key, Math.floor(session.metadata.maxAge / 1000));
}
this.emitEvent(this.createEvent('session_updated', sessionId));
}
async deleteSession(sessionId) {
const result = await this.client.del(this.getKey(sessionId));
const deleted = result === 1;
if (deleted) {
this.emitEvent(this.createEvent('session_deleted', sessionId));
}
return deleted;
}
async getSessions(filter) {
const keys = await this.client.keys(`${this.prefix}*`);
const sessions = [];
for (const key of keys) {
const data = await this.client.get(key);
if (!data)
continue;
try {
const session = JSON.parse(data);
if (this.matchesFilter(session, filter)) {
session.startTime = new Date(session.startTime);
session.lastActivity = new Date(session.lastActivity);
session.context.memoryBank = session.context.memoryBank.map(memory => ({
...memory,
timestamp: new Date(memory.timestamp)
}));
sessions.push(session);
}
}
catch (error) {
this.emitEvent(this.createEvent('error', error));
}
}
return sessions;
}
async deleteSessions(filter) {
const sessions = await this.getSessions(filter);
let deleted = 0;
for (const session of sessions) {
const success = await this.deleteSession(session.sessionId);
if (success)
deleted++;
}
return deleted;
}
async cleanup(maxAge) {
this.emitEvent(this.createEvent('cleanup_started'));
const now = Date.now();
const sessions = await this.getSessions();
let deleted = 0;
for (const session of sessions) {
const age = now - session.lastActivity.getTime();
if (age > maxAge) {
const success = await this.deleteSession(session.sessionId);
if (success)
deleted++;
}
}
this.emitEvent(this.createEvent('cleanup_completed', deleted));
}
async healthCheck() {
try {
await this.client.ping();
return true;
}
catch {
return false;
}
}
async getStats() {
const sessions = await this.getSessions();
const now = Date.now();
const oneDayAgo = now - 24 * 60 * 60 * 1000;
const activeSessions = sessions.filter(s => s.lastActivity.getTime() > oneDayAgo);
const durations = sessions.map(s => s.lastActivity.getTime() - s.startTime.getTime());
const avgDuration = durations.length > 0
? durations.reduce((a, b) => a + b, 0) / durations.length
: 0;
const modalityCount = sessions.reduce((acc, s) => {
acc[s.currentModality]++;
return acc;
}, { text: 0, voice: 0 });
const storageSize = sessions.reduce((size, session) => {
return size + JSON.stringify(session).length;
}, 0);
return {
totalSessions: sessions.length,
activeSessionsLast24h: activeSessions.length,
averageSessionDuration: avgDuration,
modalityDistribution: modalityCount,
storageSize
};
}
on(handler) {
this.eventHandlers.add(handler);
}
off(handler) {
this.eventHandlers.delete(handler);
}
getKey(sessionId) {
return `${this.prefix}${sessionId}`;
}
emitEvent(event) {
this.eventHandlers.forEach(handler => handler(event));
}
createEvent(type, data) {
switch (type) {
case 'session_created':
case 'session_updated':
case 'session_deleted':
return { type, sessionId: data };
case 'cleanup_completed':
return { type, deletedCount: data };
case 'error':
return { type, error: data };
default:
return { type };
}
}
matchesFilter(session, filter) {
if (!filter)
return true;
if (filter.userId && session.userId !== filter.userId) {
return false;
}
if (filter.modality && session.currentModality !== filter.modality) {
return false;
}
if (filter.startTime) {
const startTime = new Date(session.startTime).getTime();
if (filter.startTime.from && startTime < filter.startTime.from.getTime()) {
return false;
}
if (filter.startTime.to && startTime > filter.startTime.to.getTime()) {
return false;
}
}
if (filter.lastActivity) {
const lastActivity = new Date(session.lastActivity).getTime();
if (filter.lastActivity.from && lastActivity < filter.lastActivity.from.getTime()) {
return false;
}
if (filter.lastActivity.to && lastActivity > filter.lastActivity.to.getTime()) {
return false;
}
}
return true;
}
async shutdown() {
if (this.cleanupTimer) {
clearInterval(this.cleanupTimer);
}
await this.client.quit();
}
}
exports.RedisStorageProvider = RedisStorageProvider;
//# sourceMappingURL=RedisStorageProvider.js.map