Skip to main content
Glama

Office MCP Server

by walkingzzzy
ConversationManager.ts9.15 kB
import { ChatMessage as OpenAIMessage } from './OpenAIService'; import { ClaudeMessage } from './ClaudeService'; import logger from '../utils/logger'; export interface ConversationMessage { id: string; role: 'system' | 'user' | 'assistant' | 'function' | 'tool'; content: string; timestamp: number; metadata?: { toolCalls?: any[]; functionCall?: any; model?: string; tokens?: number; }; } export interface Conversation { id: string; title: string; messages: ConversationMessage[]; context: Record<string, any>; createdAt: number; updatedAt: number; model: 'openai' | 'claude'; maxMessages?: number; } export class ConversationManager { private conversations = new Map<string, Conversation>(); private defaultMaxMessages = 50; /** * 创建新对话 */ createConversation( title: string, model: 'openai' | 'claude' = 'openai', systemPrompt?: string ): Conversation { const conversation: Conversation = { id: this.generateId(), title, messages: [], context: {}, createdAt: Date.now(), updatedAt: Date.now(), model, maxMessages: this.defaultMaxMessages }; if (systemPrompt) { conversation.messages.push({ id: this.generateId(), role: 'system', content: systemPrompt, timestamp: Date.now() }); } this.conversations.set(conversation.id, conversation); logger.info('创建新对话', { conversationId: conversation.id, model }); return conversation; } /** * 获取对话 */ getConversation(conversationId: string): Conversation | undefined { return this.conversations.get(conversationId); } /** * 添加消息到对话 */ addMessage( conversationId: string, role: ConversationMessage['role'], content: string, metadata?: ConversationMessage['metadata'] ): ConversationMessage | null { const conversation = this.conversations.get(conversationId); if (!conversation) { logger.error('对话不存在', { conversationId }); return null; } const message: ConversationMessage = { id: this.generateId(), role, content, timestamp: Date.now(), metadata }; conversation.messages.push(message); conversation.updatedAt = Date.now(); // 限制消息数量 this.enforceMessageLimit(conversation); // 自动更新标题 if (conversation.messages.length === 2 && role === 'user') { conversation.title = this.generateTitle(content); } logger.debug('添加消息到对话', { conversationId, messageId: message.id, role }); return message; } /** * 更新消息 */ updateMessage( conversationId: string, messageId: string, updates: Partial<ConversationMessage> ): boolean { const conversation = this.conversations.get(conversationId); if (!conversation) return false; const messageIndex = conversation.messages.findIndex(m => m.id === messageId); if (messageIndex === -1) return false; conversation.messages[messageIndex] = { ...conversation.messages[messageIndex], ...updates }; conversation.updatedAt = Date.now(); return true; } /** * 删除消息 */ deleteMessage(conversationId: string, messageId: string): boolean { const conversation = this.conversations.get(conversationId); if (!conversation) return false; const messageIndex = conversation.messages.findIndex(m => m.id === messageId); if (messageIndex === -1) return false; conversation.messages.splice(messageIndex, 1); conversation.updatedAt = Date.now(); return true; } /** * 获取对话消息(适配OpenAI格式) */ getOpenAIMessages(conversationId: string): OpenAIMessage[] { const conversation = this.conversations.get(conversationId); if (!conversation) return []; return conversation.messages.map(msg => ({ role: msg.role as any, content: msg.content, ...(msg.metadata?.functionCall && { function_call: msg.metadata.functionCall }), ...(msg.role === 'function' && { name: msg.metadata?.functionCall?.name }) })); } /** * 获取对话消息(适配Claude格式) */ getClaudeMessages(conversationId: string): ClaudeMessage[] { const conversation = this.conversations.get(conversationId); if (!conversation) return []; const messages: ClaudeMessage[] = []; const nonSystemMessages = conversation.messages.filter(m => m.role !== 'system'); for (const msg of nonSystemMessages) { if (msg.role === 'user' || msg.role === 'assistant') { messages.push({ role: msg.role, content: msg.content }); } } return messages; } /** * 设置对话上下文 */ setContext(conversationId: string, key: string, value: any): boolean { const conversation = this.conversations.get(conversationId); if (!conversation) return false; conversation.context[key] = value; conversation.updatedAt = Date.now(); return true; } /** * 获取对话上下文 */ getContext(conversationId: string, key?: string): any { const conversation = this.conversations.get(conversationId); if (!conversation) return null; return key ? conversation.context[key] : conversation.context; } /** * 清空对话消息 */ clearMessages(conversationId: string, keepSystem = true): boolean { const conversation = this.conversations.get(conversationId); if (!conversation) return false; if (keepSystem) { conversation.messages = conversation.messages.filter(m => m.role === 'system'); } else { conversation.messages = []; } conversation.updatedAt = Date.now(); return true; } /** * 删除对话 */ deleteConversation(conversationId: string): boolean { const deleted = this.conversations.delete(conversationId); if (deleted) { logger.info('删除对话', { conversationId }); } return deleted; } /** * 获取所有对话 */ getAllConversations(): Conversation[] { return Array.from(this.conversations.values()) .sort((a, b) => b.updatedAt - a.updatedAt); } /** * 搜索对话 */ searchConversations(query: string): Conversation[] { const queryLower = query.toLowerCase(); return Array.from(this.conversations.values()) .filter(conv => conv.title.toLowerCase().includes(queryLower) || conv.messages.some(msg => msg.content.toLowerCase().includes(queryLower) ) ) .sort((a, b) => b.updatedAt - a.updatedAt); } /** * 限制消息数量 */ private enforceMessageLimit(conversation: Conversation): void { const maxMessages = conversation.maxMessages || this.defaultMaxMessages; if (conversation.messages.length > maxMessages) { // 保留系统消息 const systemMessages = conversation.messages.filter(m => m.role === 'system'); const otherMessages = conversation.messages.filter(m => m.role !== 'system'); // 保留最新的消息 const keepCount = maxMessages - systemMessages.length; const keptMessages = otherMessages.slice(-keepCount); conversation.messages = [...systemMessages, ...keptMessages]; } } /** * 生成对话标题 */ private generateTitle(firstMessage: string): string { const maxLength = 30; const cleaned = firstMessage.replace(/\n/g, ' ').trim(); if (cleaned.length <= maxLength) { return cleaned; } return cleaned.substring(0, maxLength - 3) + '...'; } /** * 生成唯一ID */ private generateId(): string { return `${Date.now()}_${Math.random().toString(36).substr(2, 9)}`; } /** * 获取统计信息 */ getStats(): { totalConversations: number; totalMessages: number; averageMessagesPerConversation: number; modelDistribution: Record<string, number>; } { const conversations = Array.from(this.conversations.values()); const totalMessages = conversations.reduce((sum, conv) => sum + conv.messages.length, 0); const modelDistribution: Record<string, number> = {}; for (const conv of conversations) { modelDistribution[conv.model] = (modelDistribution[conv.model] || 0) + 1; } return { totalConversations: conversations.length, totalMessages, averageMessagesPerConversation: conversations.length > 0 ? Math.round(totalMessages / conversations.length) : 0, modelDistribution }; } /** * 清理过期对话 */ cleanupExpiredConversations(maxAge: number = 7 * 24 * 60 * 60 * 1000): number { const now = Date.now(); let cleanedCount = 0; for (const [id, conversation] of this.conversations.entries()) { if (now - conversation.updatedAt > maxAge) { this.conversations.delete(id); cleanedCount++; } } if (cleanedCount > 0) { logger.info(`清理了 ${cleanedCount} 个过期对话`); } return cleanedCount; } } export default ConversationManager;

MCP directory API

We provide all the information about MCP servers via our MCP API.

curl -X GET 'https://glama.ai/api/mcp/v1/servers/walkingzzzy/office-mcp'

If you have feedback or need assistance with the MCP directory API, please join our Discord server