UNPKG

smalltalk-ai

Version:

A complete TypeScript framework for building LLM applications with agent support and MCP integration

235 lines 8.74 kB
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