smalltalk-ai
Version:
A complete TypeScript framework for building LLM applications with agent support and MCP integration
235 lines • 8.74 kB
JavaScript
import { EventEmitter } from 'events';
import { nanoid } from 'nanoid';
import { TokenJSWrapper } from '../utils/TokenJSWrapper.js';
export class Chat extends EventEmitter {
config;
llmWrapper;
sessions = new Map();
constructor(config) {
super();
this.config = config;
this.llmWrapper = new TokenJSWrapper(config);
}
createSession(sessionId) {
const id = sessionId || nanoid();
const session = {
id,
messages: [],
createdAt: new Date(),
updatedAt: new Date()
};
this.sessions.set(id, session);
this.emit('session_created', session);
return session;
}
getSession(sessionId) {
return this.sessions.get(sessionId);
}
deleteSession(sessionId) {
const session = this.sessions.get(sessionId);
if (session) {
this.sessions.delete(sessionId);
this.emit('session_deleted', session);
return true;
}
return false;
}
addMessage(sessionId, message) {
const session = this.getSession(sessionId);
if (!session) {
throw new Error(`Session ${sessionId} not found`);
}
const fullMessage = {
id: nanoid(),
timestamp: new Date(),
...message
};
session.messages.push(fullMessage);
session.updatedAt = new Date();
this.emit('message_added', { session, message: fullMessage });
return fullMessage;
}
getMessages(sessionId, limit) {
const session = this.getSession(sessionId);
if (!session) {
return [];
}
if (limit && limit > 0) {
return session.messages.slice(-limit);
}
return [...session.messages];
}
clearMessages(sessionId) {
const session = this.getSession(sessionId);
if (session) {
session.messages = [];
session.updatedAt = new Date();
this.emit('messages_cleared', session);
}
}
async generateResponse(sessionId, userMessage, context) {
const session = this.getSession(sessionId);
if (!session) {
throw new Error(`Session ${sessionId} not found`);
}
// Add user message
const message = this.addMessage(sessionId, {
role: 'user',
content: userMessage
});
try {
// Generate response using the agent if available
if (context.agent) {
const response = await context.agent.generateResponse(userMessage, context);
// Add assistant response
this.addMessage(sessionId, {
role: 'assistant',
content: response,
agentName: context.agent.name
});
return response;
}
else {
// Direct LLM call without agent
const response = await this.llmWrapper.generateResponse(session.messages);
// Add assistant response
this.addMessage(sessionId, {
role: 'assistant',
content: response.content
});
return response.content;
}
}
catch (error) {
const errorMessage = `Error generating response: ${error instanceof Error ? error.message : String(error)}`;
// Add error message to session
this.addMessage(sessionId, {
role: 'system',
content: errorMessage
});
throw error;
}
}
async generateStreamResponse(sessionId, userMessage, context, onChunk) {
const session = this.getSession(sessionId);
if (!session) {
throw new Error(`Session ${sessionId} not found`);
}
// Add user message
this.addMessage(sessionId, {
role: 'user',
content: userMessage
});
try {
// For now, streaming is handled directly by TokenJS wrapper
// TODO: Implement agent streaming support
const response = await this.llmWrapper.generateStreamResponse(session.messages, {
provider: this.config.llmProvider,
model: this.config.model,
temperature: this.config.temperature,
maxTokens: this.config.maxTokens
}, onChunk);
// Add assistant response
this.addMessage(sessionId, {
role: 'assistant',
content: response,
agentName: context.agent?.name
});
return response;
}
catch (error) {
const errorMessage = `Error generating stream response: ${error instanceof Error ? error.message : String(error)}`;
// Add error message to session
this.addMessage(sessionId, {
role: 'system',
content: errorMessage
});
throw error;
}
}
exportSession(sessionId, format = 'json') {
const session = this.getSession(sessionId);
if (!session) {
throw new Error(`Session ${sessionId} not found`);
}
if (format === 'json') {
return JSON.stringify(session, null, 2);
}
else {
// Text format
let text = `Chat Session: ${session.id}\n`;
text += `Created: ${session.createdAt.toISOString()}\n`;
text += `Updated: ${session.updatedAt.toISOString()}\n`;
text += `Messages: ${session.messages.length}\n\n`;
for (const message of session.messages) {
const timestamp = message.timestamp.toLocaleString();
const role = message.role.toUpperCase();
const agentInfo = message.agentName ? ` (${message.agentName})` : '';
text += `[${timestamp}] ${role}${agentInfo}: ${message.content}\n\n`;
}
return text;
}
}
importSession(data, format = 'json') {
if (format === 'json') {
try {
const sessionData = JSON.parse(data);
// Validate session data
if (!sessionData.id || !Array.isArray(sessionData.messages)) {
throw new Error('Invalid session data format');
}
// Ensure timestamps are Date objects
sessionData.createdAt = new Date(sessionData.createdAt);
sessionData.updatedAt = new Date(sessionData.updatedAt);
sessionData.messages = sessionData.messages.map(msg => ({
...msg,
timestamp: new Date(msg.timestamp)
}));
this.sessions.set(sessionData.id, sessionData);
this.emit('session_imported', sessionData);
return sessionData;
}
catch (error) {
throw new Error(`Failed to import session: ${error instanceof Error ? error.message : String(error)}`);
}
}
else {
throw new Error('Text format import not yet implemented');
}
}
getSessionStats(sessionId) {
const session = this.getSession(sessionId);
if (!session) {
return null;
}
const messages = session.messages;
const userMessages = messages.filter(m => m.role === 'user').length;
const assistantMessages = messages.filter(m => m.role === 'assistant' || m.role === 'agent').length;
const systemMessages = messages.filter(m => m.role === 'system').length;
const duration = session.updatedAt.getTime() - session.createdAt.getTime();
const totalLength = messages.reduce((sum, m) => sum + m.content.length, 0);
const averageMessageLength = messages.length > 0 ? totalLength / messages.length : 0;
return {
messageCount: messages.length,
userMessages,
assistantMessages,
systemMessages,
duration,
averageMessageLength
};
}
listSessions() {
return Array.from(this.sessions.values()).map(session => ({
id: session.id,
messageCount: session.messages.length,
createdAt: session.createdAt,
updatedAt: session.updatedAt,
activeAgent: session.activeAgent
}));
}
updateConfig(newConfig) {
this.config = { ...this.config, ...newConfig };
this.llmWrapper.updateConfig(this.config);
}
}
//# sourceMappingURL=Chat.js.map