From f70b32691152d580789a3ddc9b90ba49d219d263 Mon Sep 17 00:00:00 2001 From: Aditya Veer Parmar Date: Tue, 25 Feb 2025 16:12:41 +0530 Subject: [PATCH 1/4] twitter hooks --- src/clients/farcaster/interactions.ts | 2 +- src/clients/index.ts | 2 +- src/clients/twitter/index.ts | 9 ++- src/clients/twitter/interactions.ts | 104 +++++++++++++++++++++----- 4 files changed, 93 insertions(+), 24 deletions(-) diff --git a/src/clients/farcaster/interactions.ts b/src/clients/farcaster/interactions.ts index 2e96dbf4..01df38e1 100644 --- a/src/clients/farcaster/interactions.ts +++ b/src/clients/farcaster/interactions.ts @@ -97,7 +97,7 @@ export class FarcasterInteractionManager { }) if (!shouldContinue) { - elizaLogger.info('AgentcoinClient received message event but it was suppressed') + elizaLogger.info('FarcasterClient received message event but it was suppressed') return } diff --git a/src/clients/index.ts b/src/clients/index.ts index 5bb3ca3c..101d96d5 100644 --- a/src/clients/index.ts +++ b/src/clients/index.ts @@ -1,7 +1,7 @@ import { AgentcoinClientInterface } from '@/clients/agentcoin' import FarcasterClientInterface from '@/clients/farcaster' import TelegramClientInterface from '@/clients/telegram' -import { TwitterClientInterface } from '@elizaos/client-twitter' +import { TwitterClientInterface } from '@/clients/twitter' import { AgentRuntime, Character, Client, Clients } from '@elizaos/core' export async function initializeClients( diff --git a/src/clients/twitter/index.ts b/src/clients/twitter/index.ts index faa7854d..8dfaee4f 100644 --- a/src/clients/twitter/index.ts +++ b/src/clients/twitter/index.ts @@ -3,7 +3,8 @@ import { validateTwitterConfig, type TwitterConfig } from '@/clients/twitter/env import { TwitterInteractionClient } from '@/clients/twitter/interactions' import { TwitterPostClient } from '@/clients/twitter/post' import { TwitterSearchClient } from '@/clients/twitter/search' -import { elizaLogger, type Client, type IAgentRuntime } from '@elizaos/core' +import { AgentcoinRuntime } from '@/common/runtime' +import { elizaLogger, type Client } from '@elizaos/core' /** * A manager that orchestrates all specialized Twitter logic: @@ -19,7 +20,7 @@ class TwitterManager { search: TwitterSearchClient interaction: TwitterInteractionClient - constructor(runtime: IAgentRuntime, twitterConfig: TwitterConfig) { + constructor(runtime: AgentcoinRuntime, twitterConfig: TwitterConfig) { // Pass twitterConfig to the base client this.client = new ClientBase(runtime, twitterConfig) @@ -42,7 +43,7 @@ class TwitterManager { } export const TwitterClientInterface: Client = { - async start(runtime: IAgentRuntime) { + async start(runtime: AgentcoinRuntime) { const twitterConfig: TwitterConfig = await validateTwitterConfig(runtime) elizaLogger.log('Twitter client started') @@ -66,7 +67,7 @@ export const TwitterClientInterface: Client = { return manager }, - async stop(_runtime: IAgentRuntime) { + async stop(_runtime: AgentcoinRuntime) { elizaLogger.warn('Twitter client does not support stopping yet') } } diff --git a/src/clients/twitter/interactions.ts b/src/clients/twitter/interactions.ts index 69ad6ddc..e0da231e 100644 --- a/src/clients/twitter/interactions.ts +++ b/src/clients/twitter/interactions.ts @@ -1,5 +1,7 @@ import type { ClientBase } from '@/clients/twitter/base' import { buildConversationThread, sendTweet, wait } from '@/clients/twitter/utils' +import { hasActions } from '@/common/functions' +import { AgentcoinRuntime } from '@/common/runtime' import { composeContext, type Content, @@ -8,7 +10,6 @@ import { generateShouldRespond, getEmbeddingZeroVector, type HandlerCallback, - type IAgentRuntime, type IImageDescriptionService, type Memory, messageCompletionFooter, @@ -105,11 +106,11 @@ should stop participating in the conversation. export class TwitterInteractionClient { client: ClientBase - runtime: IAgentRuntime + runtime: AgentcoinRuntime private isDryRun: boolean - constructor(client: ClientBase, runtime: IAgentRuntime) { + constructor(client: ClientBase, runtime: AgentcoinRuntime) { this.client = client this.runtime = runtime this.isDryRun = this.client.twitterConfig.TWITTER_DRY_RUN @@ -234,13 +235,29 @@ export class TwitterInteractionClient { ? this.runtime.agentId : stringToUuid(tweet.userId) - await this.runtime.ensureConnection( - userIdUUID, + const messageText = tweet.text + const username = tweet.username + + const shouldContinue = await this.runtime.handle('message', { + text: messageText, + sender: username, + source: 'twitter', + timestamp: new Date(tweet.timestamp * 1000) + }) + + if (!shouldContinue) { + elizaLogger.info('TwitterClient received message event but it was suppressed') + return + } + + await this.runtime.ensureUserRoomConnection({ roomId, - tweet.username, - tweet.name, - 'twitter' - ) + userId: userIdUUID, + username, + name: tweet.name, + email: username, + source: 'twitter' + }) const thread = await buildConversationThread(tweet, this.client) @@ -298,9 +315,8 @@ export class TwitterInteractionClient { elizaLogger.log('Processing Tweet: ', tweet.id) const formatTweet = (tweet: Tweet): string => { - return ` ID: ${tweet.id} - From: ${tweet.name} (@${tweet.username}) - Text: ${tweet.text}` + return `ID: ${tweet.id} From: ${tweet.name} (@${tweet.username}) + Text: ${tweet.text}` } const currentPost = formatTweet(tweet) @@ -423,6 +439,17 @@ export class TwitterInteractionClient { twitterMessageHandlerTemplate }) + let shouldContinue = await this.runtime.handle('prellm', { + state, + responses: [], + memory: message + }) + + if (!shouldContinue) { + elizaLogger.info('TwitterClient received prellm event but it was suppressed') + return + } + const response = await generateMessageResponse({ runtime: this.runtime, context, @@ -437,6 +464,18 @@ export class TwitterInteractionClient { response.text = removeQuotes(response.text) + shouldContinue = await this.runtime.handle('postllm', { + state, + responses: [], + memory: message, + content: response + }) + + if (!shouldContinue) { + elizaLogger.info('TwitterClient received postllm event but it was suppressed') + return + } + if (response.text) { if (this.isDryRun) { elizaLogger.info( @@ -456,24 +495,53 @@ export class TwitterInteractionClient { return memories } - const responseMessages = await callback(response) + const messageResponses = await callback(response) state = await this.runtime.updateRecentMessageState(state) - for (const responseMessage of responseMessages) { - if (responseMessage === responseMessages[responseMessages.length - 1]) { + for (const responseMessage of messageResponses) { + if (responseMessage === messageResponses[messageResponses.length - 1]) { responseMessage.content.action = response.action } else { responseMessage.content.action = 'CONTINUE' } await this.runtime.messageManager.createMemory(responseMessage) } - const responseTweetId = responseMessages[responseMessages.length - 1]?.content?.tweetId + const responseTweetId = messageResponses[messageResponses.length - 1]?.content?.tweetId + + if (!hasActions(messageResponses)) { + return + } + + // `preaction` event + shouldContinue = await this.runtime.handle('preaction', { + state, + responses: messageResponses, + memory: message + }) + + if (!shouldContinue) { + elizaLogger.info('TwitterClient received preaction event but it was suppressed') + return + } + await this.runtime.processActions( message, - responseMessages, + messageResponses, state, - (response: Content) => { + async (response: Content) => { + shouldContinue = await this.runtime.handle('postaction', { + state, + responses: messageResponses, + memory: message, + content: response + }) + + if (!shouldContinue) { + elizaLogger.info('TwitterClient received postaction event but it was suppressed') + return + } + return callback(response, responseTweetId) } ) From 5be0f690b8015531c8fdc4cd63e2a11fb79ad4c8 Mon Sep 17 00:00:00 2001 From: Aditya Veer Parmar Date: Tue, 25 Feb 2025 16:32:16 +0530 Subject: [PATCH 2/4] log --- src/clients/twitter/index.ts | 2 ++ 1 file changed, 2 insertions(+) diff --git a/src/clients/twitter/index.ts b/src/clients/twitter/index.ts index 8dfaee4f..e04c3329 100644 --- a/src/clients/twitter/index.ts +++ b/src/clients/twitter/index.ts @@ -39,6 +39,8 @@ class TwitterManager { // Mentions and interactions this.interaction = new TwitterInteractionClient(this.client, runtime) + + elizaLogger.info('🐦 Twitter client initialized') } } From ed2c07a71e4b96333270d166f3679e10e3ba3f61 Mon Sep 17 00:00:00 2001 From: Aditya Veer Parmar Date: Tue, 25 Feb 2025 22:01:44 +0530 Subject: [PATCH 3/4] tweet hook --- src/clients/twitter/post.ts | 48 ++++++++++++++++++++++++++++--------- 1 file changed, 37 insertions(+), 11 deletions(-) diff --git a/src/clients/twitter/post.ts b/src/clients/twitter/post.ts index 28c81e3a..19ce820a 100644 --- a/src/clients/twitter/post.ts +++ b/src/clients/twitter/post.ts @@ -3,6 +3,7 @@ import { DEFAULT_MAX_TWEET_LENGTH } from '@/clients/twitter/environment' import { twitterMessageHandlerTemplate } from '@/clients/twitter/interactions' import { MediaData, RawTweetType } from '@/clients/twitter/types' import { buildConversationThread, fetchMediaData } from '@/clients/twitter/utils' +import { AgentcoinRuntime } from '@/common/runtime' import { ActionResponse, cleanJsonResponse, @@ -12,7 +13,6 @@ import { generateText, generateTweetActions, getEmbeddingZeroVector, - type IAgentRuntime, type IImageDescriptionService, ModelClass, parseJSONObjectFromText, @@ -94,7 +94,7 @@ type PendingTweetApprovalStatus = 'PENDING' | 'APPROVED' | 'REJECTED' export class TwitterPostClient { client: ClientBase - runtime: IAgentRuntime + runtime: AgentcoinRuntime twitterUsername: string private isProcessing = false private lastProcessTime = 0 @@ -105,7 +105,7 @@ export class TwitterPostClient { private discordApprovalChannelId: string private approvalCheckInterval: number - constructor(client: ClientBase, runtime: IAgentRuntime) { + constructor(client: ClientBase, runtime: AgentcoinRuntime) { this.client = client this.runtime = runtime this.twitterUsername = this.client.twitterConfig.TWITTER_USERNAME @@ -147,6 +147,7 @@ export class TwitterPostClient { // Initialize Discord webhook const approvalRequired: boolean = + // eslint-disable-next-line @typescript-eslint/no-unsafe-member-access this.runtime.getSetting('TWITTER_APPROVAL_ENABLED')?.toLocaleLowerCase() === 'true' if (approvalRequired) { const discordToken = this.runtime.getSetting('TWITTER_APPROVAL_DISCORD_BOT_TOKEN') @@ -297,7 +298,7 @@ export class TwitterPostClient { } async processAndCacheTweet( - runtime: IAgentRuntime, + runtime: AgentcoinRuntime, client: ClientBase, tweet: Tweet, roomId: UUID, @@ -403,7 +404,7 @@ export class TwitterPostClient { } async postTweet( - runtime: IAgentRuntime, + runtime: AgentcoinRuntime, client: ClientBase, tweetTextForPosting: string, roomId: UUID, @@ -439,12 +440,14 @@ export class TwitterPostClient { try { const roomId = stringToUuid('twitter_generate_room-' + this.client.profile.username) - await this.runtime.ensureUserExists( - this.runtime.agentId, - this.client.profile.username, - this.runtime.character.name, - 'twitter' - ) + + await this.runtime.ensureUserRoomConnection({ + roomId, + userId: this.runtime.agentId, + username: this.client.profile.username, + name: this.client.profile.username, + source: 'twitter' + }) const topics = this.runtime.character.topics.join(', ') const maxTweetLength = this.client.twitterConfig.MAX_TWEET_LENGTH @@ -464,6 +467,17 @@ export class TwitterPostClient { } ) + let shouldContinue = await this.runtime.handle('prellm', { + state, + responses: [], + memory: null + }) + + if (!shouldContinue) { + elizaLogger.info('AgentcoinClient received prellm event but it was suppressed') + return + } + const context = composeContext({ state, template: this.runtime.character.templates?.twitterPostTemplate || twitterPostTemplate @@ -477,6 +491,18 @@ export class TwitterPostClient { modelClass: ModelClass.SMALL }) + shouldContinue = await this.runtime.handle('postllm', { + state, + responses: [], + memory: null, + content: { text: response } + }) + + if (!shouldContinue) { + elizaLogger.info('AgentcoinClient received postllm event but it was suppressed') + return + } + const rawTweetContent = cleanJsonResponse(response) // First attempt to clean content From 3cf3b60d789542ccbe832d12cdc1d956e6e377d0 Mon Sep 17 00:00:00 2001 From: Aditya Veer Parmar Date: Thu, 27 Feb 2025 14:19:40 +0530 Subject: [PATCH 4/4] roomId --- src/clients/twitter/post.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/clients/twitter/post.ts b/src/clients/twitter/post.ts index 19ce820a..8946fa01 100644 --- a/src/clients/twitter/post.ts +++ b/src/clients/twitter/post.ts @@ -439,7 +439,7 @@ export class TwitterPostClient { elizaLogger.log('Generating new tweet') try { - const roomId = stringToUuid('twitter_generate_room-' + this.client.profile.username) + const roomId = stringToUuid('user_twitter_feed:' + this.client.profile.username) await this.runtime.ensureUserRoomConnection({ roomId,