UNPKG

@restnfeel/agentc-starter-kit

Version:

한국어 기업용 CMS 모듈 - Task Master AI와 함께 빠르게 웹사이트를 구현할 수 있는 재사용 가능한 컴포넌트 시스템

290 lines (242 loc) 7.36 kB
import { ChatMessage, ConversationContext } from "../types"; export interface ConversationConfig { maxMessages: number; maxTokens?: number; retainSystemMessages: boolean; contextTimeoutMs?: number; } export interface MessageSummary { totalMessages: number; userMessages: number; assistantMessages: number; systemMessages: number; totalTokens: number; } export class ConversationContextManager { private conversations: Map<string, ConversationContext> = new Map(); private config: ConversationConfig; constructor(config: Partial<ConversationConfig> = {}) { this.config = { maxMessages: 20, maxTokens: 4000, retainSystemMessages: true, contextTimeoutMs: 30 * 60 * 1000, // 30 minutes ...config, }; } createConversation(id: string, systemPrompt?: string): ConversationContext { const context: ConversationContext = { id, messages: [], createdAt: new Date(), updatedAt: new Date(), metadata: {}, }; if (systemPrompt) { context.messages.push({ id: this.generateMessageId(), role: "system", content: systemPrompt, timestamp: new Date(), }); } this.conversations.set(id, context); return context; } getConversation(id: string): ConversationContext | null { const conversation = this.conversations.get(id); if (!conversation) { return null; } // Check if conversation has expired if (this.config.contextTimeoutMs) { const now = Date.now(); const lastUpdate = conversation.updatedAt.getTime(); if (now - lastUpdate > this.config.contextTimeoutMs) { this.conversations.delete(id); return null; } } return conversation; } addMessage( conversationId: string, message: Omit<ChatMessage, "id" | "timestamp"> ): ChatMessage { let conversation = this.getConversation(conversationId); if (!conversation) { // Create new conversation if it doesn't exist conversation = this.createConversation(conversationId); } const chatMessage: ChatMessage = { ...message, id: this.generateMessageId(), timestamp: new Date(), }; conversation.messages.push(chatMessage); conversation.updatedAt = new Date(); // Trim conversation if it exceeds limits this.trimConversation(conversation); return chatMessage; } updateMessage( conversationId: string, messageId: string, updates: Partial<ChatMessage> ): boolean { const conversation = this.getConversation(conversationId); if (!conversation) { return false; } const messageIndex = conversation.messages.findIndex( (msg) => msg.id === messageId ); if (messageIndex === -1) { return false; } conversation.messages[messageIndex] = { ...conversation.messages[messageIndex], ...updates, updatedAt: new Date(), }; conversation.updatedAt = new Date(); return true; } getConversationHistory( conversationId: string, lastN?: number ): ChatMessage[] { const conversation = this.getConversation(conversationId); if (!conversation) { return []; } const messages = conversation.messages; if (lastN && lastN > 0) { return messages.slice(-lastN); } return messages; } getConversationSummary(conversationId: string): MessageSummary | null { const conversation = this.getConversation(conversationId); if (!conversation) { return null; } const summary: MessageSummary = { totalMessages: conversation.messages.length, userMessages: 0, assistantMessages: 0, systemMessages: 0, totalTokens: 0, }; for (const message of conversation.messages) { switch (message.role) { case "user": summary.userMessages++; break; case "assistant": summary.assistantMessages++; break; case "system": summary.systemMessages++; break; } // Simple token estimation (4 chars ≈ 1 token) summary.totalTokens += Math.ceil(message.content.length / 4); } return summary; } clearConversation(conversationId: string): boolean { return this.conversations.delete(conversationId); } getAllConversations(): ConversationContext[] { return Array.from(this.conversations.values()); } cleanupExpiredConversations(): number { if (!this.config.contextTimeoutMs) { return 0; } const now = Date.now(); let cleaned = 0; for (const [id, conversation] of this.conversations) { const lastUpdate = conversation.updatedAt.getTime(); if (now - lastUpdate > this.config.contextTimeoutMs) { this.conversations.delete(id); cleaned++; } } return cleaned; } private trimConversation(conversation: ConversationContext): void { const messages = conversation.messages; // Separate system messages from conversation messages const systemMessages = messages.filter((msg) => msg.role === "system"); const conversationMessages = messages.filter( (msg) => msg.role !== "system" ); // Trim by message count if (conversationMessages.length > this.config.maxMessages) { const keepCount = this.config.maxMessages; conversationMessages.splice(0, conversationMessages.length - keepCount); } // Trim by token count if specified if (this.config.maxTokens) { let totalTokens = 0; const trimmedMessages: ChatMessage[] = []; // Count tokens from the end (most recent messages) for (let i = conversationMessages.length - 1; i >= 0; i--) { const messageTokens = Math.ceil( conversationMessages[i].content.length / 4 ); if (totalTokens + messageTokens <= this.config.maxTokens) { trimmedMessages.unshift(conversationMessages[i]); totalTokens += messageTokens; } else { break; } } conversationMessages.splice( 0, conversationMessages.length, ...trimmedMessages ); } // Reconstruct conversation with system messages if (this.config.retainSystemMessages) { conversation.messages = [...systemMessages, ...conversationMessages]; } else { conversation.messages = conversationMessages; } } private generateMessageId(): string { return `msg_${Date.now()}_${Math.random().toString(36).substr(2, 9)}`; } // Update conversation metadata updateConversationMetadata( conversationId: string, metadata: Record<string, any> ): boolean { const conversation = this.getConversation(conversationId); if (!conversation) { return false; } conversation.metadata = { ...conversation.metadata, ...metadata }; conversation.updatedAt = new Date(); return true; } // Get statistics getManagerStats(): { totalConversations: number; totalMessages: number; config: ConversationConfig; } { let totalMessages = 0; for (const conversation of this.conversations.values()) { totalMessages += conversation.messages.length; } return { totalConversations: this.conversations.size, totalMessages, config: this.config, }; } }