From e47b1d986ca7de495146098f012f2e951c19efa8 Mon Sep 17 00:00:00 2001 From: Ting Chien Meng Date: Wed, 18 Dec 2024 19:14:07 -0500 Subject: [PATCH] clean code --- .../client-telegram/src/messageManager.ts | 371 ++++++------------ 1 file changed, 126 insertions(+), 245 deletions(-) diff --git a/packages/client-telegram/src/messageManager.ts b/packages/client-telegram/src/messageManager.ts index a79f1160cde..68b710c2a9f 100644 --- a/packages/client-telegram/src/messageManager.ts +++ b/packages/client-telegram/src/messageManager.ts @@ -1,5 +1,5 @@ import { Message } from "@telegraf/types"; -import { Context, Telegraf, TelegramBot } from "telegraf"; +import { Context, Telegraf } from "telegraf"; import { composeContext, elizaLogger, ServiceType } from "@ai16z/eliza"; import { getEmbeddingZeroVector } from "@ai16z/eliza"; @@ -24,7 +24,7 @@ import { MESSAGE_CONSTANTS, TIMING_CONSTANTS, RESPONSE_CHANCES, - TEAM_COORDINATION, + TEAM_COORDINATION } from "./constants"; import fs from "fs"; @@ -169,35 +169,25 @@ export class MessageManager { this.bot = bot; this.runtime = runtime; - this._initializeTeamMemberUsernames().catch((error) => - elizaLogger.error( - "Error initializing team member usernames:", - error - ) + this._initializeTeamMemberUsernames().catch(error => + elizaLogger.error("Error initializing team member usernames:", error) ); } private async _initializeTeamMemberUsernames(): Promise { - if (!this.runtime.character.clientConfig?.telegram?.isPartOfTeam) - return; + if (!this.runtime.character.clientConfig?.telegram?.isPartOfTeam) return; - const teamAgentIds = - this.runtime.character.clientConfig.telegram.teamAgentIds || []; + const teamAgentIds = this.runtime.character.clientConfig.telegram.teamAgentIds || []; for (const id of teamAgentIds) { try { const chat = await this.bot.telegram.getChat(id); - if ("username" in chat && chat.username) { + if ('username' in chat && chat.username) { this.teamMemberUsernames.set(id, chat.username); - elizaLogger.info( - `Cached username for team member ${id}: ${chat.username}` - ); + elizaLogger.info(`Cached username for team member ${id}: ${chat.username}`); } } catch (error) { - elizaLogger.error( - `Error getting username for team member ${id}:`, - error - ); + elizaLogger.error(`Error getting username for team member ${id}:`, error); } } } @@ -207,7 +197,7 @@ export class MessageManager { } private _getNormalizedUserId(id: string | number): string { - return id.toString().replace(/[^0-9]/g, ""); + return id.toString().replace(/[^0-9]/g, ''); } private _isTeamMember(userId: string | number): boolean { @@ -215,30 +205,23 @@ export class MessageManager { if (!teamConfig?.isPartOfTeam || !teamConfig.teamAgentIds) return false; const normalizedUserId = this._getNormalizedUserId(userId); - return teamConfig.teamAgentIds.some( - (teamId) => this._getNormalizedUserId(teamId) === normalizedUserId + return teamConfig.teamAgentIds.some(teamId => + this._getNormalizedUserId(teamId) === normalizedUserId ); } private _isTeamLeader(): boolean { - return ( - this.bot.botInfo?.id.toString() === - this.runtime.character.clientConfig?.telegram?.teamLeaderId - ); + return this.bot.botInfo?.id.toString() === this.runtime.character.clientConfig?.telegram?.teamLeaderId; } private _isTeamCoordinationRequest(content: string): boolean { const contentLower = content.toLowerCase(); - return TEAM_COORDINATION.KEYWORDS?.some((keyword) => + return TEAM_COORDINATION.KEYWORDS?.some(keyword => contentLower.includes(keyword.toLowerCase()) ); } - private _isRelevantToTeamMember( - content: string, - chatId: string, - lastAgentMemory: Memory | null = null - ): boolean { + private _isRelevantToTeamMember(content: string, chatId: string, lastAgentMemory: Memory | null = null): boolean { const teamConfig = this.runtime.character.clientConfig?.telegram; // Check leader's context based on last message @@ -253,10 +236,7 @@ export class MessageManager { lastAgentMemory.content.text.toLowerCase() ); - return ( - similarity >= - MESSAGE_CONSTANTS.DEFAULT_SIMILARITY_THRESHOLD_FOLLOW_UPS - ); + return similarity >= MESSAGE_CONSTANTS.DEFAULT_SIMILARITY_THRESHOLD_FOLLOW_UPS; } // Check team member keywords @@ -265,20 +245,16 @@ export class MessageManager { } // Check if content matches any team member keywords - return teamConfig.teamMemberInterestKeywords.some((keyword) => + return teamConfig.teamMemberInterestKeywords.some(keyword => content.toLowerCase().includes(keyword.toLowerCase()) ); } - private async _analyzeContextSimilarity( - currentMessage: string, - previousContext?: MessageContext, - agentLastMessage?: string - ): Promise { + private async _analyzeContextSimilarity(currentMessage: string, previousContext?: MessageContext, agentLastMessage?: string): Promise { if (!previousContext) return 1; const timeDiff = Date.now() - previousContext.timestamp; - const timeWeight = Math.max(0, 1 - timeDiff / (5 * 60 * 1000)); + const timeWeight = Math.max(0, 1 - (timeDiff / (5 * 60 * 1000))); const similarity = cosineSimilarity( currentMessage.toLowerCase(), @@ -289,16 +265,9 @@ export class MessageManager { return similarity * timeWeight; } - private async _shouldRespondBasedOnContext( - message: Message, - chatState: InterestChats[string] - ): Promise { - const messageText = - "text" in message - ? message.text - : "caption" in message - ? (message as any).caption - : ""; + private async _shouldRespondBasedOnContext(message: Message, chatState: InterestChats[string]): Promise { + const messageText = 'text' in message ? message.text : + 'caption' in message ? (message as any).caption : ''; if (!messageText) return false; @@ -306,46 +275,42 @@ export class MessageManager { if (this._isMessageForMe(message)) return true; // If we're not the current handler, don't respond - if (chatState?.currentHandler !== this.bot.botInfo?.id.toString()) - return false; + if (chatState?.currentHandler !== this.bot.botInfo?.id.toString()) return false; // Check if we have messages to compare if (!chatState.messages?.length) return false; // Get last user message (not from the bot) - const lastUserMessage = [...chatState.messages].reverse().find( - (m, index) => + const lastUserMessage = [...chatState.messages] + .reverse() + .find((m, index) => index > 0 && // Skip first message (current) m.userId !== this.runtime.agentId - ); + ); if (!lastUserMessage) return false; const lastSelfMemories = await this.runtime.messageManager.getMemories({ - roomId: stringToUuid( - message.chat.id.toString() + "-" + this.runtime.agentId - ), + roomId: stringToUuid(message.chat.id.toString() + "-" + this.runtime.agentId), unique: false, - count: 5, + count: 5 }); - const lastSelfSortedMemories = lastSelfMemories - ?.filter((m) => m.userId === this.runtime.agentId) + const lastSelfSortedMemories = lastSelfMemories?.filter(m => m.userId === this.runtime.agentId) .sort((a, b) => (b.createdAt || 0) - (a.createdAt || 0)); // Calculate context similarity const contextSimilarity = await this._analyzeContextSimilarity( messageText, { - content: lastUserMessage.content.text || "", - timestamp: Date.now(), + content: lastUserMessage.content.text || '', + timestamp: Date.now() }, lastSelfSortedMemories?.[0]?.content?.text ); const similarityThreshold = - this.runtime.character.clientConfig?.telegram - ?.messageSimilarityThreshold || + this.runtime.character.clientConfig?.telegram?.messageSimilarityThreshold || chatState.contextSimilarityThreshold || MESSAGE_CONSTANTS.DEFAULT_SIMILARITY_THRESHOLD; @@ -356,31 +321,19 @@ export class MessageManager { const botUsername = this.bot.botInfo?.username; if (!botUsername) return false; - const messageText = - "text" in message - ? message.text - : "caption" in message - ? (message as any).caption - : ""; + const messageText = 'text' in message ? message.text : + 'caption' in message ? (message as any).caption : ''; if (!messageText) return false; - const isReplyToBot = - (message as any).reply_to_message?.from?.is_bot === true && - (message as any).reply_to_message?.from?.username === botUsername; + const isReplyToBot = (message as any).reply_to_message?.from?.is_bot === true && + (message as any).reply_to_message?.from?.username === botUsername; const isMentioned = messageText.includes(`@${botUsername}`); - const hasUsername = messageText - .toLowerCase() - .includes(botUsername.toLowerCase()); - - return ( - isReplyToBot || - isMentioned || - (!this.runtime.character.clientConfig?.telegram - ?.shouldRespondOnlyToMentions && - hasUsername) - ); + const hasUsername = messageText.toLowerCase().includes(botUsername.toLowerCase()); + + return isReplyToBot || isMentioned || (!this.runtime.character.clientConfig?.telegram?.shouldRespondOnlyToMentions && hasUsername); } + private _checkInterest(chatId: string): boolean { const chatState = this.interestChats[chatId]; if (!chatState) return false; @@ -391,30 +344,17 @@ export class MessageManager { if (timeSinceLastMessage > MESSAGE_CONSTANTS.INTEREST_DECAY_TIME) { delete this.interestChats[chatId]; return false; - } else if ( - timeSinceLastMessage > MESSAGE_CONSTANTS.PARTIAL_INTEREST_DECAY - ) { - return this._isRelevantToTeamMember( - lastMessage?.content.text || "", - chatId - ); + } else if (timeSinceLastMessage > MESSAGE_CONSTANTS.PARTIAL_INTEREST_DECAY) { + return this._isRelevantToTeamMember(lastMessage?.content.text || '', chatId); } // Team leader specific checks if (this._isTeamLeader() && chatState.messages.length > 0) { - if ( - !this._isRelevantToTeamMember( - lastMessage?.content.text || "", - chatId - ) - ) { - const recentTeamResponses = chatState.messages - .slice(-3) - .some( - (m) => - m.userId !== this.runtime.agentId && - this._isTeamMember(m.userId.toString()) - ); + if (!this._isRelevantToTeamMember(lastMessage?.content.text || '', chatId)) { + const recentTeamResponses = chatState.messages.slice(-3).some(m => + m.userId !== this.runtime.agentId && + this._isTeamMember(m.userId.toString()) + ); if (recentTeamResponses) { delete this.interestChats[chatId]; @@ -433,7 +373,7 @@ export class MessageManager { try { let imageUrl: string | null = null; - elizaLogger.info(`Telegram Message: ${message}`); + elizaLogger.info(`Telegram Message: ${message}`) if ("photo" in message && message.photo?.length > 0) { const photo = message.photo[message.photo.length - 1]; @@ -472,10 +412,8 @@ export class MessageManager { message: Message, state: State ): Promise { - if ( - this.runtime.character.clientConfig?.telegram - ?.shouldRespondOnlyToMentions - ) { + + if (this.runtime.character.clientConfig?.telegram?.shouldRespondOnlyToMentions) { return this._isMessageForMe(message); } @@ -484,7 +422,7 @@ export class MessageManager { "text" in message && message.text?.includes(`@${this.bot.botInfo?.username}`) ) { - elizaLogger.info(`Bot mentioned`); + elizaLogger.info(`Bot mentioned`) return true; } @@ -504,62 +442,41 @@ export class MessageManager { const chatId = message.chat.id.toString(); const chatState = this.interestChats[chatId]; - const messageText = - "text" in message - ? message.text - : "caption" in message - ? (message as any).caption - : ""; + const messageText = 'text' in message ? message.text : + 'caption' in message ? (message as any).caption : ''; // Check if team member has direct interest first - if ( - this.runtime.character.clientConfig?.discord?.isPartOfTeam && + if (this.runtime.character.clientConfig?.discord?.isPartOfTeam && !this._isTeamLeader() && - this._isRelevantToTeamMember(messageText, chatId) - ) { + this._isRelevantToTeamMember(messageText, chatId)) { + return true; } // Team-based response logic if (this.runtime.character.clientConfig?.telegram?.isPartOfTeam) { // Team coordination - if (this._isTeamCoordinationRequest(messageText)) { + if(this._isTeamCoordinationRequest(messageText)) { if (this._isTeamLeader()) { return true; } else { - const randomDelay = - Math.floor( - Math.random() * - (TIMING_CONSTANTS.TEAM_MEMBER_DELAY_MAX - - TIMING_CONSTANTS.TEAM_MEMBER_DELAY_MIN) - ) + TIMING_CONSTANTS.TEAM_MEMBER_DELAY_MIN; // 1-3 second random delay - await new Promise((resolve) => - setTimeout(resolve, randomDelay) - ); + const randomDelay = Math.floor(Math.random() * (TIMING_CONSTANTS.TEAM_MEMBER_DELAY_MAX - TIMING_CONSTANTS.TEAM_MEMBER_DELAY_MIN)) + + TIMING_CONSTANTS.TEAM_MEMBER_DELAY_MIN; // 1-3 second random delay + await new Promise(resolve => setTimeout(resolve, randomDelay)); return true; } } - if ( - !this._isTeamLeader() && - this._isRelevantToTeamMember(messageText, chatId) - ) { + if (!this._isTeamLeader() && this._isRelevantToTeamMember(messageText, chatId)) { // Add small delay for non-leader responses - await new Promise((resolve) => - setTimeout(resolve, TIMING_CONSTANTS.TEAM_MEMBER_DELAY) - ); //1.5 second delay + await new Promise(resolve => setTimeout(resolve, TIMING_CONSTANTS.TEAM_MEMBER_DELAY)); //1.5 second delay // If leader has responded in last few seconds, reduce chance of responding if (chatState.messages?.length) { - const recentMessages = chatState.messages.slice( - -MESSAGE_CONSTANTS.RECENT_MESSAGE_COUNT - ); - const leaderResponded = recentMessages.some( - (m) => - m.userId === - this.runtime.character.clientConfig?.telegram - ?.teamLeaderId && - Date.now() - chatState.lastMessageSent < 3000 + const recentMessages = chatState.messages.slice(-MESSAGE_CONSTANTS.RECENT_MESSAGE_COUNT); + const leaderResponded = recentMessages.some(m => + m.userId === this.runtime.character.clientConfig?.telegram?.teamLeaderId && + Date.now() - chatState.lastMessageSent < 3000 ); if (leaderResponded) { @@ -572,29 +489,17 @@ export class MessageManager { } // If I'm the leader but message doesn't match my keywords, add delay and check for team responses - if ( - this._isTeamLeader() && - !this._isRelevantToTeamMember(messageText, chatId) - ) { - const randomDelay = - Math.floor( - Math.random() * - (TIMING_CONSTANTS.LEADER_DELAY_MAX - - TIMING_CONSTANTS.LEADER_DELAY_MIN) - ) + TIMING_CONSTANTS.LEADER_DELAY_MIN; // 2-4 second random delay - await new Promise((resolve) => - setTimeout(resolve, randomDelay) - ); + if (this._isTeamLeader() && !this._isRelevantToTeamMember(messageText, chatId)) { + const randomDelay = Math.floor(Math.random() * (TIMING_CONSTANTS.LEADER_DELAY_MAX - TIMING_CONSTANTS.LEADER_DELAY_MIN)) + + TIMING_CONSTANTS.LEADER_DELAY_MIN; // 2-4 second random delay + await new Promise(resolve => setTimeout(resolve, randomDelay)); // After delay, check if another team member has already responded if (chatState?.messages?.length) { - const recentResponses = chatState.messages.slice( - -MESSAGE_CONSTANTS.RECENT_MESSAGE_COUNT - ); - const otherTeamMemberResponded = recentResponses.some( - (m) => - m.userId !== this.runtime.agentId && - this._isTeamMember(m.userId) + const recentResponses = chatState.messages.slice(-MESSAGE_CONSTANTS.RECENT_MESSAGE_COUNT); + const otherTeamMemberResponded = recentResponses.some(m => + m.userId !== this.runtime.agentId && + this._isTeamMember(m.userId) ); if (otherTeamMemberResponded) { @@ -607,8 +512,7 @@ export class MessageManager { if (this._isMessageForMe(message)) { const channelState = this.interestChats[chatId]; if (channelState) { - channelState.currentHandler = - this.bot.botInfo?.id.toString(); + channelState.currentHandler = this.bot.botInfo?.id.toString() channelState.lastMessageSent = Date.now(); } return true; @@ -616,43 +520,43 @@ export class MessageManager { // Don't respond if another teammate is handling the conversation if (chatState?.currentHandler) { - if ( - chatState.currentHandler !== - this.bot.botInfo?.id.toString() && - this._isTeamMember(chatState.currentHandler) - ) { + if (chatState.currentHandler !== this.bot.botInfo?.id.toString() && + this._isTeamMember(chatState.currentHandler)) { return false; } } // Natural conversation cadence if (!this._isMessageForMe(message) && this.interestChats[chatId]) { - const recentMessages = this.interestChats[ - chatId - ].messages.slice(-MESSAGE_CONSTANTS.CHAT_HISTORY_COUNT); - const ourMessageCount = recentMessages.filter( - (m) => m.userId === this.runtime.agentId + + const recentMessages = this.interestChats[chatId].messages + .slice(-MESSAGE_CONSTANTS.CHAT_HISTORY_COUNT); + const ourMessageCount = recentMessages.filter(m => + m.userId === this.runtime.agentId ).length; if (ourMessageCount > 2) { + const responseChance = Math.pow(0.5, ourMessageCount - 2); if (Math.random() > responseChance) { return; } } } + } // Check context-based response for team conversations if (chatState?.currentHandler) { - const shouldRespondContext = - await this._shouldRespondBasedOnContext(message, chatState); + const shouldRespondContext = await this._shouldRespondBasedOnContext(message, chatState); if (!shouldRespondContext) { return false; } + } + // Use AI to decide for text or captions if ("text" in message || ("caption" in message && message.caption)) { const shouldRespondContext = composeContext({ @@ -711,6 +615,7 @@ export class MessageManager { return sentMessages; } } + private async sendImage( ctx: Context, imagePath: string, @@ -810,50 +715,40 @@ export class MessageManager { const message = ctx.message; const chatId = ctx.chat?.id.toString(); - const messageText = - "text" in message - ? message.text - : "caption" in message - ? (message as any).caption - : ""; + const messageText = 'text' in message ? message.text : + 'caption' in message ? (message as any).caption : ''; // Add team handling at the start - if ( - this.runtime.character.clientConfig?.telegram?.isPartOfTeam && - !this.runtime.character.clientConfig?.telegram - ?.shouldRespondOnlyToMentions - ) { + if (this.runtime.character.clientConfig?.telegram?.isPartOfTeam && + !this.runtime.character.clientConfig?.telegram?.shouldRespondOnlyToMentions) { + const isDirectlyMentioned = this._isMessageForMe(message); const hasInterest = this._checkInterest(chatId); + // Non-leader team member showing interest based on keywords - if ( - !this._isTeamLeader() && - this._isRelevantToTeamMember(messageText, chatId) - ) { + if (!this._isTeamLeader() && this._isRelevantToTeamMember(messageText, chatId)) { + this.interestChats[chatId] = { currentHandler: this.bot.botInfo?.id.toString(), lastMessageSent: Date.now(), - messages: [], + messages: [] }; } const isTeamRequest = this._isTeamCoordinationRequest(messageText); const isLeader = this._isTeamLeader(); + // Check for continued interest if (hasInterest && !isDirectlyMentioned) { - const lastSelfMemories = - await this.runtime.messageManager.getMemories({ - roomId: stringToUuid( - chatId + "-" + this.runtime.agentId - ), - unique: false, - count: 5, - }); - - const lastSelfSortedMemories = lastSelfMemories - ?.filter((m) => m.userId === this.runtime.agentId) + const lastSelfMemories = await this.runtime.messageManager.getMemories({ + roomId: stringToUuid(chatId + "-" + this.runtime.agentId), + unique: false, + count: 5 + }); + + const lastSelfSortedMemories = lastSelfMemories?.filter(m => m.userId === this.runtime.agentId) .sort((a, b) => (b.createdAt || 0) - (a.createdAt || 0)); const isRelevant = this._isRelevantToTeamMember( @@ -874,39 +769,35 @@ export class MessageManager { this.interestChats[chatId] = { currentHandler: this.bot.botInfo?.id.toString(), lastMessageSent: Date.now(), - messages: [], + messages: [] }; } else { this.interestChats[chatId] = { currentHandler: this.bot.botInfo?.id.toString(), lastMessageSent: Date.now(), - messages: [], + messages: [] }; if (!isDirectlyMentioned) { this.interestChats[chatId].lastMessageSent = 0; } + } } // Check for other team member mentions using cached usernames - const otherTeamMembers = - this.runtime.character.clientConfig.telegram.teamAgentIds.filter( - (id) => id !== this.bot.botInfo?.id.toString() - ); + const otherTeamMembers = this.runtime.character.clientConfig.telegram.teamAgentIds.filter( + id => id !== this.bot.botInfo?.id.toString() + ); - const mentionedTeamMember = otherTeamMembers.find((id) => { + const mentionedTeamMember = otherTeamMembers.find(id => { const username = this._getTeamMemberUsername(id); return username && messageText?.includes(`@${username}`); }); // If another team member is mentioned, clear our interest if (mentionedTeamMember) { - if ( - hasInterest || - this.interestChats[chatId]?.currentHandler === - this.bot.botInfo?.id.toString() - ) { + if (hasInterest || this.interestChats[chatId]?.currentHandler === this.bot.botInfo?.id.toString()) { delete this.interestChats[chatId]; // Only return if we're not the mentioned member @@ -921,7 +812,7 @@ export class MessageManager { this.interestChats[chatId] = { currentHandler: this.bot.botInfo?.id.toString(), lastMessageSent: Date.now(), - messages: [], + messages: [] }; } else if (!isTeamRequest && !hasInterest) { return; @@ -931,20 +822,13 @@ export class MessageManager { if (this.interestChats[chatId]) { this.interestChats[chatId].messages.push({ userId: stringToUuid(ctx.from.id.toString()), - userName: - ctx.from.username || - ctx.from.first_name || - "Unknown User", - content: { text: messageText, source: "telegram" }, + userName: ctx.from.username || ctx.from.first_name || "Unknown User", + content: { text: messageText, source: "telegram" } }); - if ( - this.interestChats[chatId].messages.length > - MESSAGE_CONSTANTS.MAX_MESSAGES - ) { - this.interestChats[chatId].messages = this.interestChats[ - chatId - ].messages.slice(-MESSAGE_CONSTANTS.MAX_MESSAGES); + if (this.interestChats[chatId].messages.length > MESSAGE_CONSTANTS.MAX_MESSAGES) { + this.interestChats[chatId].messages = + this.interestChats[chatId].messages.slice(-MESSAGE_CONSTANTS.MAX_MESSAGES); } } } @@ -1064,7 +948,6 @@ export class MessageManager { content, message.message_id ); - if (sentMessages) { const memories: Memory[] = []; @@ -1072,7 +955,7 @@ export class MessageManager { for (let i = 0; i < sentMessages.length; i++) { const sentMessage = sentMessages[i]; const isLastMessage = i === sentMessages.length - 1; - + const memory: Memory = { id: stringToUuid( sentMessage.message_id.toString() + @@ -1090,19 +973,17 @@ export class MessageManager { createdAt: sentMessage.date * 1000, embedding: getEmbeddingZeroVector(), }; - + // Set action to CONTINUE for all messages except the last one // For the last message, use the original action from the response content memory.content.action = !isLastMessage ? "CONTINUE" : content.action; - - await this.runtime.messageManager.createMemory( - memory - ); + + await this.runtime.messageManager.createMemory(memory); memories.push(memory); } - + return memories; } }; @@ -1128,4 +1009,4 @@ export class MessageManager { elizaLogger.error("Error sending message:", error); } } -} +} \ No newline at end of file