From aad36f256f67440dc45fb7404e14ef941702502d Mon Sep 17 00:00:00 2001 From: Bruno Galego Date: Thu, 21 Nov 2024 03:07:36 +0000 Subject: [PATCH 1/9] fix ids --- packages/client-twitter/src/base.ts | 28 ++++++++++++--------- packages/client-twitter/src/interactions.ts | 11 ++++---- packages/client-twitter/src/post.ts | 22 +++++++--------- packages/client-twitter/src/search.ts | 2 +- packages/client-twitter/src/utils.ts | 4 +-- 5 files changed, 32 insertions(+), 35 deletions(-) diff --git a/packages/client-twitter/src/base.ts b/packages/client-twitter/src/base.ts index 21313890d4f..d483619e74f 100644 --- a/packages/client-twitter/src/base.ts +++ b/packages/client-twitter/src/base.ts @@ -112,6 +112,7 @@ export class ClientBase extends EventEmitter { const tweet = await this.requestQueue.add(() => this.twitterClient.getTweet(tweetId) ); + await this.cacheTweet(tweet); return tweet; } @@ -343,20 +344,24 @@ export class ClientBase extends EventEmitter { // Check if any of the cached tweets exist in the existing memories const someCachedTweetsExist = cachedTimeline.some((tweet) => - existingMemoryIds.has(tweet.id) + existingMemoryIds.has( + stringToUuid(tweet.id + "-" + this.runtime.agentId) + ) ); if (someCachedTweetsExist) { // Filter out the cached tweets that already exist in the database const tweetsToSave = cachedTimeline.filter( - (tweet) => !existingMemoryIds.has(tweet.id) + (tweet) => + !existingMemoryIds.has( + stringToUuid(tweet.id + "-" + this.runtime.agentId) + ) ); // Save the missing tweets as memories for (const tweet of tweetsToSave) { const roomId = stringToUuid( - tweet.conversationId ?? - "default-room-" + this.runtime.agentId + tweet.conversationId + "-" + this.runtime.agentId ); const tweetuserId = tweet.userId === this.twitterUserId @@ -428,27 +433,26 @@ export class ClientBase extends EventEmitter { // Create a Set to store unique tweet IDs const tweetIdsToCheck = new Set(); + const roomIds = new Set(); // Add tweet IDs to the Set for (const tweet of allTweets) { tweetIdsToCheck.add(tweet.id); + roomIds.add( + stringToUuid(tweet.conversationId + "-" + this.runtime.agentId) + ); } - // Convert the Set to an array of UUIDs - const tweetUuids = Array.from(tweetIdsToCheck).map((id) => - stringToUuid(id + "-" + this.runtime.agentId) - ); - // Check the existing memories in the database const existingMemories = await this.runtime.messageManager.getMemoriesByRoomIds({ agentId: this.runtime.agentId, - roomIds: tweetUuids, + roomIds: Array.from(roomIds), }); // Create a Set to store the existing memory IDs const existingMemoryIds = new Set( - existingMemories.map((memory) => memory.roomId) + existingMemories.map((memory) => memory.id) ); // Filter out the tweets that already exist in the database @@ -469,7 +473,7 @@ export class ClientBase extends EventEmitter { // Save the new tweets as memories for (const tweet of tweetsToSave) { const roomId = stringToUuid( - tweet.conversationId ?? "default-room-" + this.runtime.agentId + tweet.conversationId + "-" + this.runtime.agentId ); const tweetuserId = tweet.userId === this.twitterUserId diff --git a/packages/client-twitter/src/interactions.ts b/packages/client-twitter/src/interactions.ts index 616f9664af5..b5f46a350c4 100644 --- a/packages/client-twitter/src/interactions.ts +++ b/packages/client-twitter/src/interactions.ts @@ -14,7 +14,7 @@ import { stringToUuid, elizaLogger, } from "@ai16z/eliza"; -import { ClientBase } from "./base.ts"; +import { ClientBase } from "./base"; import { buildConversationThread, sendTweet, wait } from "./utils.ts"; import { embeddingZeroVector } from "@ai16z/eliza"; @@ -127,12 +127,11 @@ export class TwitterInteractionClient extends ClientBase { !this.lastCheckedTweetId || parseInt(tweet.id) > this.lastCheckedTweetId ) { - const conversationId = - tweet.conversationId + "-" + this.runtime.agentId; - - const roomId = stringToUuid(conversationId); + const roomId = stringToUuid( + tweet.conversationId + "-" + this.runtime.agentId + ); - const userIdUUID = stringToUuid(tweet.userId as string); + const userIdUUID = stringToUuid(tweet.userId!); await this.runtime.ensureConnection( userIdUUID, diff --git a/packages/client-twitter/src/post.ts b/packages/client-twitter/src/post.ts index a9b1c995d8f..9cb7e0c5fa7 100644 --- a/packages/client-twitter/src/post.ts +++ b/packages/client-twitter/src/post.ts @@ -7,12 +7,7 @@ import { ModelClass, stringToUuid, } from "@ai16z/eliza"; -import fs from "fs"; -import { composeContext, elizaLogger } from "@ai16z/eliza"; -import { generateText } from "@ai16z/eliza"; -import { embeddingZeroVector } from "@ai16z/eliza"; -import { IAgentRuntime, ModelClass } from "@ai16z/eliza"; -import { stringToUuid } from "@ai16z/eliza"; +import { elizaLogger } from "@ai16z/eliza"; import { ClientBase } from "./base.ts"; const twitterPostTemplate = `{{timeline}} @@ -163,8 +158,10 @@ export class TwitterPostClient extends ClientBase { // Use the helper function to truncate to complete sentence const content = truncateToCompleteSentence(formattedTweet); - if (this.runtime.getSetting("TWITTER_DRY_RUN") === 'true') { - elizaLogger.info(`Dry run: would have posted tweet: ${content}`); + if (this.runtime.getSetting("TWITTER_DRY_RUN") === "true") { + elizaLogger.info( + `Dry run: would have posted tweet: ${content}` + ); return; } @@ -192,10 +189,9 @@ export class TwitterPostClient extends ClientBase { videos: [], } as Tweet; - const postId = tweet.id; - const conversationId = - tweet.conversationId + "-" + this.runtime.agentId; - const roomId = stringToUuid(conversationId); + const roomId = stringToUuid( + tweet.conversationId + "-" + this.runtime.agentId + ); await this.runtime.ensureRoomExists(roomId); await this.runtime.ensureParticipantInRoom( @@ -206,7 +202,7 @@ export class TwitterPostClient extends ClientBase { await this.cacheTweet(tweet); await this.runtime.messageManager.createMemory({ - id: stringToUuid(postId + "-" + this.runtime.agentId), + id: stringToUuid(tweet.id + "-" + this.runtime.agentId), userId: this.runtime.agentId, agentId: this.runtime.agentId, content: { diff --git a/packages/client-twitter/src/search.ts b/packages/client-twitter/src/search.ts index feaaed19b9f..b12d1d4bfba 100644 --- a/packages/client-twitter/src/search.ts +++ b/packages/client-twitter/src/search.ts @@ -12,7 +12,7 @@ import { State, } from "@ai16z/eliza"; import { stringToUuid } from "@ai16z/eliza"; -import { ClientBase } from "./base.ts"; +import { ClientBase } from "./base"; import { buildConversationThread, sendTweet, wait } from "./utils.ts"; const twitterSearchTemplate = diff --git a/packages/client-twitter/src/utils.ts b/packages/client-twitter/src/utils.ts index 5849c71c6bb..5cc3dad7bff 100644 --- a/packages/client-twitter/src/utils.ts +++ b/packages/client-twitter/src/utils.ts @@ -1,10 +1,8 @@ -// utils.ts - import { Tweet } from "agent-twitter-client"; import { embeddingZeroVector } from "@ai16z/eliza"; import { Content, Memory, UUID } from "@ai16z/eliza"; import { stringToUuid } from "@ai16z/eliza"; -import { ClientBase } from "./base.ts"; +import { ClientBase } from "./base"; import { elizaLogger } from "@ai16z/eliza"; const MAX_TWEET_LENGTH = 280; // Updated to Twitter's current character limit From c0af0b62d8902cb21fd6bc4b1722d4873bc5beb1 Mon Sep 17 00:00:00 2001 From: Bruno Galego Date: Thu, 21 Nov 2024 04:03:03 +0000 Subject: [PATCH 2/9] use the same client for post and interactions --- packages/client-twitter/src/base.ts | 159 ++++++++++---------- packages/client-twitter/src/index.ts | 18 ++- packages/client-twitter/src/interactions.ts | 60 +++++--- packages/client-twitter/src/post.ts | 31 ++-- 4 files changed, 148 insertions(+), 120 deletions(-) diff --git a/packages/client-twitter/src/base.ts b/packages/client-twitter/src/base.ts index d483619e74f..bdf29173c36 100644 --- a/packages/client-twitter/src/base.ts +++ b/packages/client-twitter/src/base.ts @@ -125,7 +125,7 @@ export class ClientBase extends EventEmitter { ); } - constructor({ runtime }: { runtime: IAgentRuntime }) { + constructor(runtime: IAgentRuntime) { super(); this.runtime = runtime; if (ClientBase._twitterClient) { @@ -140,94 +140,91 @@ export class ClientBase extends EventEmitter { this.runtime.character.style.all.join("\n- ") + "- " + this.runtime.character.style.post.join(); + } - // async initialization - (async () => { - //test - await this.loadCachedLatestCheckedTweetId(); - // Check for Twitter cookies - if (this.runtime.getSetting("TWITTER_COOKIES")) { - const cookiesArray = JSON.parse( - this.runtime.getSetting("TWITTER_COOKIES") - ); + async init() { + //test + await this.loadCachedLatestCheckedTweetId(); + // Check for Twitter cookies + if (this.runtime.getSetting("TWITTER_COOKIES")) { + const cookiesArray = JSON.parse( + this.runtime.getSetting("TWITTER_COOKIES") + ); - await this.setCookiesFromArray(cookiesArray); + await this.setCookiesFromArray(cookiesArray); + } else { + const cachedCookies = await this.getCachedCookies(); + if (cachedCookies) { + await this.setCookiesFromArray(cachedCookies); } else { - const cachedCookies = await this.getCachedCookies(); - if (cachedCookies) { - await this.setCookiesFromArray(cachedCookies); - } else { - await this.twitterClient.login( - this.runtime.getSetting("TWITTER_USERNAME"), - this.runtime.getSetting("TWITTER_PASSWORD"), - this.runtime.getSetting("TWITTER_EMAIL"), - this.runtime.getSetting("TWITTER_2FA_SECRET") - ); - elizaLogger.log("Logged in to Twitter"); - const cookies = await this.twitterClient.getCookies(); - await this.cacheCookies(cookies); - } + await this.twitterClient.login( + this.runtime.getSetting("TWITTER_USERNAME"), + this.runtime.getSetting("TWITTER_PASSWORD"), + this.runtime.getSetting("TWITTER_EMAIL"), + this.runtime.getSetting("TWITTER_2FA_SECRET") + ); + elizaLogger.log("Logged in to Twitter"); + const cookies = await this.twitterClient.getCookies(); + await this.cacheCookies(cookies); } + } - let loggedInWaits = 0; - - while (!(await this.twitterClient.isLoggedIn())) { - console.log("Waiting for Twitter login"); - await new Promise((resolve) => setTimeout(resolve, 2000)); - if (loggedInWaits > 10) { - console.error("Failed to login to Twitter"); - await this.twitterClient.login( - this.runtime.getSetting("TWITTER_USERNAME"), - this.runtime.getSetting("TWITTER_PASSWORD"), - this.runtime.getSetting("TWITTER_EMAIL"), - this.runtime.getSetting("TWITTER_2FA_SECRET") - ); - const cookies = await this.twitterClient.getCookies(); - await this.cacheCookies(cookies); - loggedInWaits = 0; - } - loggedInWaits++; - } - const userId = await this.requestQueue.add(async () => { - // wait 3 seconds before getting the user id - await new Promise((resolve) => setTimeout(resolve, 10000)); - try { - return await this.twitterClient.getUserIdByScreenName( - this.runtime.getSetting("TWITTER_USERNAME") - ); - } catch (error) { - console.error("Error getting user ID:", error); - return null; - } - }); - if (!userId) { - console.error("Failed to get user ID"); - return; + let loggedInWaits = 0; + + while (!(await this.twitterClient.isLoggedIn())) { + console.log("Waiting for Twitter login"); + await new Promise((resolve) => setTimeout(resolve, 2000)); + if (loggedInWaits > 10) { + console.error("Failed to login to Twitter"); + await this.twitterClient.login( + this.runtime.getSetting("TWITTER_USERNAME"), + this.runtime.getSetting("TWITTER_PASSWORD"), + this.runtime.getSetting("TWITTER_EMAIL"), + this.runtime.getSetting("TWITTER_2FA_SECRET") + ); + const cookies = await this.twitterClient.getCookies(); + await this.cacheCookies(cookies); + loggedInWaits = 0; } - elizaLogger.log("Twitter user ID:", userId); - this.twitterUserId = userId; - - // Initialize Twitter profile - const profile = await this.initializeProfile(); - if (profile) { - // console.log("Twitter profile initialized:", profile); - - // Store profile info for use in responses - this.runtime.character = { - ...this.runtime.character, - twitterProfile: { - username: profile.username, - screenName: profile.screenName, - bio: profile.bio, - nicknames: profile.nicknames, - }, - }; + loggedInWaits++; + } + const userId = await this.requestQueue.add(async () => { + // wait 3 seconds before getting the user id + await new Promise((resolve) => setTimeout(resolve, 10000)); + try { + return await this.twitterClient.getUserIdByScreenName( + this.runtime.getSetting("TWITTER_USERNAME") + ); + } catch (error) { + console.error("Error getting user ID:", error); + return null; } + }); + if (!userId) { + console.error("Failed to get user ID"); + return; + } + elizaLogger.log("Twitter user ID:", userId); + this.twitterUserId = userId; + + // Initialize Twitter profile + const profile = await this.initializeProfile(); + if (profile) { + // console.log("Twitter profile initialized:", profile); + + // Store profile info for use in responses + this.runtime.character = { + ...this.runtime.character, + twitterProfile: { + username: profile.username, + screenName: profile.screenName, + bio: profile.bio, + nicknames: profile.nicknames, + }, + }; + } - await this.populateTimeline(); - - this.onReady(); - })(); + await this.populateTimeline(); } async fetchHomeTimeline(count: number): Promise { diff --git a/packages/client-twitter/src/index.ts b/packages/client-twitter/src/index.ts index 742b5ac34dc..332f35441ef 100644 --- a/packages/client-twitter/src/index.ts +++ b/packages/client-twitter/src/index.ts @@ -2,25 +2,35 @@ import { TwitterPostClient } from "./post.ts"; import { TwitterSearchClient } from "./search.ts"; import { TwitterInteractionClient } from "./interactions.ts"; import { IAgentRuntime, Client, elizaLogger } from "@ai16z/eliza"; +import { ClientBase } from "./base.ts"; -class TwitterAllClient { +class TwitterManager { + client: ClientBase; post: TwitterPostClient; search: TwitterSearchClient; interaction: TwitterInteractionClient; constructor(runtime: IAgentRuntime) { - this.post = new TwitterPostClient(runtime); + this.client = new ClientBase(runtime); + this.post = new TwitterPostClient(this.client, runtime); // this.search = new TwitterSearchClient(runtime); // don't start the search client by default // this searches topics from character file, but kind of violates consent of random users // burns your rate limit and can get your account banned // use at your own risk - this.interaction = new TwitterInteractionClient(runtime); + this.interaction = new TwitterInteractionClient(this.client, runtime); } } export const TwitterClientInterface: Client = { async start(runtime: IAgentRuntime) { elizaLogger.log("Twitter client started"); - return new TwitterAllClient(runtime); + const manager = new TwitterManager(runtime); + + await manager.client.init(); + + await this.post.start(); + await this.interaction.start(); + + return manager; }, async stop(runtime: IAgentRuntime) { elizaLogger.warn("Twitter client does not support stopping yet"); diff --git a/packages/client-twitter/src/interactions.ts b/packages/client-twitter/src/interactions.ts index b5f46a350c4..1e19699fdd0 100644 --- a/packages/client-twitter/src/interactions.ts +++ b/packages/client-twitter/src/interactions.ts @@ -82,8 +82,16 @@ Thread of Tweets You Are Replying To: # INSTRUCTIONS: Respond with [RESPOND] if {{agentName}} should respond, or [IGNORE] if {{agentName}} should not respond to the last message and [STOP] if {{agentName}} should stop participating in the conversation. ` + shouldRespondFooter; -export class TwitterInteractionClient extends ClientBase { - onReady() { +export class TwitterInteractionClient { + client: ClientBase; + runtime: IAgentRuntime; + + constructor(client: ClientBase, runtime: IAgentRuntime) { + this.client = client; + this.runtime = runtime; + } + + async start() { const handleTwitterInteractionsLoop = () => { this.handleTwitterInteractions(); setTimeout( @@ -94,19 +102,15 @@ export class TwitterInteractionClient extends ClientBase { handleTwitterInteractionsLoop(); } - constructor(runtime: IAgentRuntime) { - super({ - runtime, - }); - } - async handleTwitterInteractions() { elizaLogger.log("Checking Twitter interactions"); + + const twitterUsername = this.runtime.getSetting("TWITTER_USERNAME"); try { // Check for mentions const tweetCandidates = ( - await this.fetchSearchTweets( - `@${this.runtime.getSetting("TWITTER_USERNAME")}`, + await this.client.fetchSearchTweets( + `@${twitterUsername}`, 20, SearchMode.Latest ) @@ -115,23 +119,30 @@ export class TwitterInteractionClient extends ClientBase { // de-duplicate tweetCandidates with a set const uniqueTweetCandidates = [...new Set(tweetCandidates)]; + console.log({ twitterUserId: this.client.twitterUserId }); + // Sort tweet candidates by ID in ascending order uniqueTweetCandidates .sort((a, b) => a.id.localeCompare(b.id)) - .filter((tweet) => tweet.userId !== this.twitterUserId); + .filter((tweet) => tweet.userId !== this.client.twitterUserId); // for each tweet candidate, handle the tweet for (const tweet of uniqueTweetCandidates) { // console.log("tweet:", tweet); if ( - !this.lastCheckedTweetId || - parseInt(tweet.id) > this.lastCheckedTweetId + !this.client.lastCheckedTweetId || + parseInt(tweet.id) > this.client.lastCheckedTweetId ) { + elizaLogger.log("New Tweet found", tweet.permanentUrl); + const roomId = stringToUuid( tweet.conversationId + "-" + this.runtime.agentId ); - const userIdUUID = stringToUuid(tweet.userId!); + const userIdUUID = + tweet.userId === this.client.twitterUserId + ? this.runtime.agentId + : stringToUuid(tweet.userId!); await this.runtime.ensureConnection( userIdUUID, @@ -141,7 +152,10 @@ export class TwitterInteractionClient extends ClientBase { "twitter" ); - const thread = await buildConversationThread(tweet, this); + const thread = await buildConversationThread( + tweet, + this.client + ); const message = { content: { text: tweet.text }, @@ -157,12 +171,12 @@ export class TwitterInteractionClient extends ClientBase { }); // Update the last checked tweet ID after processing each tweet - this.lastCheckedTweetId = parseInt(tweet.id); + this.client.lastCheckedTweetId = parseInt(tweet.id); } } // Save the latest checked tweet ID to the file - await this.cacheLatestCheckedTweetId(); + await this.client.cacheLatestCheckedTweetId(); elizaLogger.log("Finished checking Twitter interactions"); } catch (error) { @@ -200,12 +214,12 @@ export class TwitterInteractionClient extends ClientBase { let homeTimeline: Tweet[] = []; // read the file if it exists - const cachedTimeline = await this.getCachedTimeline(); + const cachedTimeline = await this.client.getCachedTimeline(); if (cachedTimeline) { homeTimeline = cachedTimeline; } else { - homeTimeline = await this.fetchHomeTimeline(50); - await this.cacheTimeline(homeTimeline); + homeTimeline = await this.client.fetchHomeTimeline(50); + await this.client.cacheTimeline(homeTimeline); } elizaLogger.debug("Thread: ", thread); @@ -234,7 +248,7 @@ export class TwitterInteractionClient extends ClientBase { .join("\n"); let state = await this.runtime.composeState(message, { - twitterClient: this.twitterClient, + twitterClient: this.client.twitterClient, twitterUserName: this.runtime.getSetting("TWITTER_USERNAME"), currentPost, formattedConversation, @@ -269,7 +283,7 @@ export class TwitterInteractionClient extends ClientBase { roomId, createdAt: tweet.timestamp * 1000, }; - this.saveRequestMessage(message, state); + this.client.saveRequestMessage(message, state); } const shouldRespondContext = composeContext({ @@ -321,7 +335,7 @@ export class TwitterInteractionClient extends ClientBase { try { const callback: HandlerCallback = async (response: Content) => { const memories = await sendTweet( - this, + this.client, response, message.roomId, this.runtime.getSetting("TWITTER_USERNAME"), diff --git a/packages/client-twitter/src/post.ts b/packages/client-twitter/src/post.ts index 9cb7e0c5fa7..20110730996 100644 --- a/packages/client-twitter/src/post.ts +++ b/packages/client-twitter/src/post.ts @@ -62,8 +62,11 @@ function truncateToCompleteSentence(text: string): string { return text.slice(0, MAX_TWEET_LENGTH - 3).trim() + "..."; } -export class TwitterPostClient extends ClientBase { - onReady(postImmediately: boolean = true) { +export class TwitterPostClient { + client: ClientBase; + runtime: IAgentRuntime; + + async start(postImmediately: boolean = true) { const generateNewTweetLoop = () => { const minMinutes = parseInt(this.runtime.getSetting("POST_INTERVAL_MIN")) || 90; @@ -88,10 +91,9 @@ export class TwitterPostClient extends ClientBase { generateNewTweetLoop(); } - constructor(runtime: IAgentRuntime) { - super({ - runtime, - }); + constructor(client: ClientBase, runtime: IAgentRuntime) { + this.client = client; + this.runtime = runtime; } private async generateNewTweet() { @@ -106,13 +108,13 @@ export class TwitterPostClient extends ClientBase { let homeTimeline = []; - const cachedTimeline = await this.getCachedTimeline(); + const cachedTimeline = await this.client.getCachedTimeline(); if (cachedTimeline) { homeTimeline = cachedTimeline; } else { - homeTimeline = await this.fetchHomeTimeline(50); - this.cacheTimeline(homeTimeline); + homeTimeline = await this.client.fetchHomeTimeline(50); + this.client.cacheTimeline(homeTimeline); } const formattedHomeTimeline = @@ -166,8 +168,11 @@ export class TwitterPostClient extends ClientBase { } try { - const result = await this.requestQueue.add( - async () => await this.twitterClient.sendTweet(content) + elizaLogger.log(`Posting new tweet:\n ${content}`); + + const result = await this.client.requestQueue.add( + async () => + await this.client.twitterClient.sendTweet(content) ); const body = await result.json(); const tweetResult = body.data.create_tweet.tweet_results.result; @@ -189,6 +194,8 @@ export class TwitterPostClient extends ClientBase { videos: [], } as Tweet; + elizaLogger.log(`Tweet posted:\n ${tweet.permanentUrl}`); + const roomId = stringToUuid( tweet.conversationId + "-" + this.runtime.agentId ); @@ -199,7 +206,7 @@ export class TwitterPostClient extends ClientBase { roomId ); - await this.cacheTweet(tweet); + await this.client.cacheTweet(tweet); await this.runtime.messageManager.createMemory({ id: stringToUuid(tweet.id + "-" + this.runtime.agentId), From bb2c4ba93bbda48ee6b1cbac9dacf9842079b2c4 Mon Sep 17 00:00:00 2001 From: Bruno Galego Date: Thu, 21 Nov 2024 04:17:50 +0000 Subject: [PATCH 3/9] fix --- packages/client-twitter/src/index.ts | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/packages/client-twitter/src/index.ts b/packages/client-twitter/src/index.ts index 332f35441ef..5bed063a1f9 100644 --- a/packages/client-twitter/src/index.ts +++ b/packages/client-twitter/src/index.ts @@ -27,8 +27,8 @@ export const TwitterClientInterface: Client = { await manager.client.init(); - await this.post.start(); - await this.interaction.start(); + await manager.post.start(); + await manager.interaction.start(); return manager; }, From 4ebfbb55a6b80aef3af5a09e154e0b4a95632389 Mon Sep 17 00:00:00 2001 From: Bruno Galego Date: Thu, 21 Nov 2024 04:28:53 +0000 Subject: [PATCH 4/9] save post schedule into cache --- packages/client-twitter/src/post.ts | 29 ++++++++++++++++++++++++++--- 1 file changed, 26 insertions(+), 3 deletions(-) diff --git a/packages/client-twitter/src/post.ts b/packages/client-twitter/src/post.ts index 20110730996..6c640e44770 100644 --- a/packages/client-twitter/src/post.ts +++ b/packages/client-twitter/src/post.ts @@ -66,8 +66,17 @@ export class TwitterPostClient { client: ClientBase; runtime: IAgentRuntime; - async start(postImmediately: boolean = true) { - const generateNewTweetLoop = () => { + async start(postImmediately: boolean = false) { + const generateNewTweetLoop = async () => { + const lastPost = await this.runtime.cacheManager.get<{ + timestamp: number; + }>( + "twitter/" + + this.runtime.getSetting("TWITTER_USERNAME") + + "/lastPost" + ); + + const lastPostTimestamp = lastPost?.timestamp ?? 0; const minMinutes = parseInt(this.runtime.getSetting("POST_INTERVAL_MIN")) || 90; const maxMinutes = @@ -77,8 +86,11 @@ export class TwitterPostClient { minMinutes; const delay = randomMinutes * 60 * 1000; + if (Date.now() > lastPostTimestamp + delay) { + await this.generateNewTweet(); + } + setTimeout(() => { - this.generateNewTweet(); generateNewTweetLoop(); // Set up next iteration }, delay); @@ -88,6 +100,7 @@ export class TwitterPostClient { if (postImmediately) { this.generateNewTweet(); } + generateNewTweetLoop(); } @@ -194,6 +207,16 @@ export class TwitterPostClient { videos: [], } as Tweet; + await this.runtime.cacheManager.set( + "twitter/" + + this.runtime.getSetting("TWITTER_USERNAME") + + "/lastPost", + { + id: tweet.id, + timestamp: Date.now(), + } + ); + elizaLogger.log(`Tweet posted:\n ${tweet.permanentUrl}`); const roomId = stringToUuid( From cbaea9ec89f741bc52626c3739911bede57e79a3 Mon Sep 17 00:00:00 2001 From: Bruno Galego Date: Thu, 21 Nov 2024 04:58:34 +0000 Subject: [PATCH 5/9] cache new generated post --- packages/client-twitter/src/post.ts | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/packages/client-twitter/src/post.ts b/packages/client-twitter/src/post.ts index 6c640e44770..51602dcebf6 100644 --- a/packages/client-twitter/src/post.ts +++ b/packages/client-twitter/src/post.ts @@ -217,6 +217,11 @@ export class TwitterPostClient { } ); + await this.client.cacheTweet(tweet); + + homeTimeline.push(tweet); + await this.client.cacheTimeline(homeTimeline); + elizaLogger.log(`Tweet posted:\n ${tweet.permanentUrl}`); const roomId = stringToUuid( From 5dc81e22bb2fc48d00dd5e9a85aee3897b803ea3 Mon Sep 17 00:00:00 2001 From: Bruno Galego Date: Thu, 21 Nov 2024 04:59:29 +0000 Subject: [PATCH 6/9] cache profile use ellizaLogger --- packages/client-twitter/src/base.ts | 38 ++++++++++++++++----- packages/client-twitter/src/interactions.ts | 23 ++++++------- packages/client-twitter/src/post.ts | 5 ++- 3 files changed, 42 insertions(+), 24 deletions(-) diff --git a/packages/client-twitter/src/base.ts b/packages/client-twitter/src/base.ts index bdf29173c36..bd78a30695e 100644 --- a/packages/client-twitter/src/base.ts +++ b/packages/client-twitter/src/base.ts @@ -23,6 +23,13 @@ export function extractAnswer(text: string): string { return text.slice(startIndex, endIndex); } +type TwitterProfile = { + username: string; + screenName: string; + bio: string; + nicknames: string[]; +}; + class RequestQueue { private queue: (() => Promise)[] = []; private processing: boolean = false; @@ -171,11 +178,11 @@ export class ClientBase extends EventEmitter { let loggedInWaits = 0; + elizaLogger.log("Waiting for Twitter login"); while (!(await this.twitterClient.isLoggedIn())) { - console.log("Waiting for Twitter login"); await new Promise((resolve) => setTimeout(resolve, 2000)); if (loggedInWaits > 10) { - console.error("Failed to login to Twitter"); + elizaLogger.error("Failed to login to Twitter"); await this.twitterClient.login( this.runtime.getSetting("TWITTER_USERNAME"), this.runtime.getSetting("TWITTER_PASSWORD"), @@ -190,28 +197,28 @@ export class ClientBase extends EventEmitter { } const userId = await this.requestQueue.add(async () => { // wait 3 seconds before getting the user id - await new Promise((resolve) => setTimeout(resolve, 10000)); + // await new Promise((resolve) => setTimeout(resolve, 10000)); try { return await this.twitterClient.getUserIdByScreenName( this.runtime.getSetting("TWITTER_USERNAME") ); } catch (error) { - console.error("Error getting user ID:", error); + elizaLogger.error("Error getting user ID:", error); return null; } }); if (!userId) { - console.error("Failed to get user ID"); + elizaLogger.error("Failed to get user ID"); return; } elizaLogger.log("Twitter user ID:", userId); this.twitterUserId = userId; // Initialize Twitter profile - const profile = await this.initializeProfile(); - if (profile) { - // console.log("Twitter profile initialized:", profile); + const profile = await this.loadProfile(); + if (profile) { + elizaLogger.log("Twitter profile loaded:", profile); // Store profile info for use in responses this.runtime.character = { ...this.runtime.character, @@ -222,6 +229,8 @@ export class ClientBase extends EventEmitter { nicknames: profile.nicknames, }, }; + } else { + throw new Error("Failed to load profile"); } await this.populateTimeline(); @@ -596,13 +605,19 @@ export class ClientBase extends EventEmitter { ); } - async initializeProfile() { + async loadProfile(): Promise { const username = this.runtime.getSetting("TWITTER_USERNAME"); if (!username) { console.error("Twitter username not configured"); return; } + const cached = await this.runtime.cacheManager.get( + `twitter/${username}/profile` + ); + + if (cached) return cached; + try { const profile = await this.requestQueue.add(async () => { const profile = await this.twitterClient.getProfile(username); @@ -621,6 +636,11 @@ export class ClientBase extends EventEmitter { }; }); + this.runtime.cacheManager.set( + `twitter/${username}/profile`, + profile + ); + return profile; } catch (error) { console.error("Error fetching Twitter profile:", error); diff --git a/packages/client-twitter/src/interactions.ts b/packages/client-twitter/src/interactions.ts index 1e19699fdd0..60d1f78b4c1 100644 --- a/packages/client-twitter/src/interactions.ts +++ b/packages/client-twitter/src/interactions.ts @@ -118,9 +118,6 @@ export class TwitterInteractionClient { // de-duplicate tweetCandidates with a set const uniqueTweetCandidates = [...new Set(tweetCandidates)]; - - console.log({ twitterUserId: this.client.twitterUserId }); - // Sort tweet candidates by ID in ascending order uniqueTweetCandidates .sort((a, b) => a.id.localeCompare(b.id)) @@ -128,7 +125,6 @@ export class TwitterInteractionClient { // for each tweet candidate, handle the tweet for (const tweet of uniqueTweetCandidates) { - // console.log("tweet:", tweet); if ( !this.client.lastCheckedTweetId || parseInt(tweet.id) > this.client.lastCheckedTweetId @@ -393,19 +389,19 @@ export class TwitterInteractionClient { const visited: Set = new Set(); async function processThread(currentTweet: Tweet, depth: number = 0) { - console.log("Processing tweet:", { + elizaLogger.log("Processing tweet:", { id: currentTweet.id, inReplyToStatusId: currentTweet.inReplyToStatusId, depth: depth, }); if (!currentTweet) { - console.log("No current tweet found for thread building"); + elizaLogger.log("No current tweet found for thread building"); return; } if (depth >= maxReplies) { - console.log("Reached maximum reply depth", depth); + elizaLogger.log("Reached maximum reply depth", depth); return; } @@ -469,7 +465,7 @@ export class TwitterInteractionClient { }); if (currentTweet.inReplyToStatusId) { - console.log( + elizaLogger.log( "Fetching parent tweet:", currentTweet.inReplyToStatusId ); @@ -479,25 +475,28 @@ export class TwitterInteractionClient { ); if (parentTweet) { - console.log("Found parent tweet:", { + elizaLogger.log("Found parent tweet:", { id: parentTweet.id, text: parentTweet.text?.slice(0, 50), }); await processThread(parentTweet, depth + 1); } else { - console.log( + elizaLogger.log( "No parent tweet found for:", currentTweet.inReplyToStatusId ); } } catch (error) { - console.log("Error fetching parent tweet:", { + elizaLogger.log("Error fetching parent tweet:", { tweetId: currentTweet.inReplyToStatusId, error, }); } } else { - console.log("Reached end of reply chain at:", currentTweet.id); + elizaLogger.log( + "Reached end of reply chain at:", + currentTweet.id + ); } } diff --git a/packages/client-twitter/src/post.ts b/packages/client-twitter/src/post.ts index 51602dcebf6..5faa1b9cac3 100644 --- a/packages/client-twitter/src/post.ts +++ b/packages/client-twitter/src/post.ts @@ -221,7 +221,6 @@ export class TwitterPostClient { homeTimeline.push(tweet); await this.client.cacheTimeline(homeTimeline); - elizaLogger.log(`Tweet posted:\n ${tweet.permanentUrl}`); const roomId = stringToUuid( @@ -250,10 +249,10 @@ export class TwitterPostClient { createdAt: tweet.timestamp * 1000, }); } catch (error) { - console.error("Error sending tweet:", error); + elizaLogger.error("Error sending tweet:", error); } } catch (error) { - console.error("Error generating new tweet:", error); + elizaLogger.error("Error generating new tweet:", error); } } } From f31ab65e8cffe471e6c7df8bf14c8d537fc497ef Mon Sep 17 00:00:00 2001 From: Bruno Galego Date: Thu, 21 Nov 2024 05:00:05 +0000 Subject: [PATCH 7/9] add log when creating memory --- packages/core/src/memory.ts | 1 + 1 file changed, 1 insertion(+) diff --git a/packages/core/src/memory.ts b/packages/core/src/memory.ts index ea91a615604..e8da49858c5 100644 --- a/packages/core/src/memory.ts +++ b/packages/core/src/memory.ts @@ -165,6 +165,7 @@ export class MemoryManager implements IMemoryManager { return; } + elizaLogger.log("Creating Memory", memory.id, memory.content.text); await this.runtime.databaseAdapter.createMemory( memory, this.tableName, From 4a0edc389324890c8267e4bb308d88dd4a95e8cf Mon Sep 17 00:00:00 2001 From: Bruno Galego Date: Thu, 21 Nov 2024 05:01:48 +0000 Subject: [PATCH 8/9] add logs when generating text --- packages/core/src/generation.ts | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/packages/core/src/generation.ts b/packages/core/src/generation.ts index 448b70518dd..a96b1106cf8 100644 --- a/packages/core/src/generation.ts +++ b/packages/core/src/generation.ts @@ -61,6 +61,8 @@ export async function generateText({ return ""; } + elizaLogger.log("Genarating text..."); + const provider = runtime.modelProvider; const endpoint = runtime.character.modelEndpointOverride || models[provider].endpoint; @@ -700,6 +702,8 @@ export async function generateMessageResponse({ let retryLength = 1000; // exponential backoff while (true) { try { + elizaLogger.log("Genarating message response.."); + const response = await generateText({ runtime, context, From 83885f87131416e3a9a99ffbafd4e4afb05f0857 Mon Sep 17 00:00:00 2001 From: Bruno Galego Date: Thu, 21 Nov 2024 05:50:18 +0000 Subject: [PATCH 9/9] use profile, other improvements --- packages/client-twitter/src/base.ts | 154 ++++++++------------ packages/client-twitter/src/interactions.ts | 13 +- packages/core/src/runtime.ts | 12 +- 3 files changed, 76 insertions(+), 103 deletions(-) diff --git a/packages/client-twitter/src/base.ts b/packages/client-twitter/src/base.ts index bd78a30695e..58c379faf5a 100644 --- a/packages/client-twitter/src/base.ts +++ b/packages/client-twitter/src/base.ts @@ -24,6 +24,7 @@ export function extractAnswer(text: string): string { } type TwitterProfile = { + id: string; username: string; screenName: string; bio: string; @@ -90,7 +91,8 @@ export class ClientBase extends EventEmitter { temperature: number = 0.5; requestQueue: RequestQueue = new RequestQueue(); - twitterUserId: string; + + profile: TwitterProfile | null; async cacheTweet(tweet: Tweet): Promise { if (!tweet) { @@ -151,7 +153,11 @@ export class ClientBase extends EventEmitter { async init() { //test - await this.loadCachedLatestCheckedTweetId(); + const username = this.runtime.getSetting("TWITTER_USERNAME"); + + if (!username) { + throw new Error("Twitter username not configured"); + } // Check for Twitter cookies if (this.runtime.getSetting("TWITTER_COOKIES")) { const cookiesArray = JSON.parse( @@ -160,79 +166,54 @@ export class ClientBase extends EventEmitter { await this.setCookiesFromArray(cookiesArray); } else { - const cachedCookies = await this.getCachedCookies(); + const cachedCookies = await this.getCachedCookies(username); if (cachedCookies) { await this.setCookiesFromArray(cachedCookies); - } else { - await this.twitterClient.login( - this.runtime.getSetting("TWITTER_USERNAME"), - this.runtime.getSetting("TWITTER_PASSWORD"), - this.runtime.getSetting("TWITTER_EMAIL"), - this.runtime.getSetting("TWITTER_2FA_SECRET") - ); - elizaLogger.log("Logged in to Twitter"); - const cookies = await this.twitterClient.getCookies(); - await this.cacheCookies(cookies); } } - let loggedInWaits = 0; - elizaLogger.log("Waiting for Twitter login"); - while (!(await this.twitterClient.isLoggedIn())) { - await new Promise((resolve) => setTimeout(resolve, 2000)); - if (loggedInWaits > 10) { - elizaLogger.error("Failed to login to Twitter"); - await this.twitterClient.login( - this.runtime.getSetting("TWITTER_USERNAME"), - this.runtime.getSetting("TWITTER_PASSWORD"), - this.runtime.getSetting("TWITTER_EMAIL"), - this.runtime.getSetting("TWITTER_2FA_SECRET") - ); + while (true) { + await this.twitterClient.login( + username, + this.runtime.getSetting("TWITTER_PASSWORD"), + this.runtime.getSetting("TWITTER_EMAIL"), + this.runtime.getSetting("TWITTER_2FA_SECRET") + ); + + if (await this.twitterClient.isLoggedIn()) { const cookies = await this.twitterClient.getCookies(); - await this.cacheCookies(cookies); - loggedInWaits = 0; - } - loggedInWaits++; - } - const userId = await this.requestQueue.add(async () => { - // wait 3 seconds before getting the user id - // await new Promise((resolve) => setTimeout(resolve, 10000)); - try { - return await this.twitterClient.getUserIdByScreenName( - this.runtime.getSetting("TWITTER_USERNAME") - ); - } catch (error) { - elizaLogger.error("Error getting user ID:", error); - return null; + await this.cacheCookies(username, cookies); + break; } - }); - if (!userId) { - elizaLogger.error("Failed to get user ID"); - return; + + elizaLogger.error("Failed to login to Twitter trying again..."); + + await new Promise((resolve) => setTimeout(resolve, 2000)); } - elizaLogger.log("Twitter user ID:", userId); - this.twitterUserId = userId; // Initialize Twitter profile - const profile = await this.loadProfile(); + this.profile = await this.fetchProfile(username); - if (profile) { - elizaLogger.log("Twitter profile loaded:", profile); + if (this.profile) { + elizaLogger.log("Twitter user ID:", this.profile.id); + elizaLogger.log( + "Twitter loaded:", + JSON.stringify(this.profile, null, 10) + ); // Store profile info for use in responses - this.runtime.character = { - ...this.runtime.character, - twitterProfile: { - username: profile.username, - screenName: profile.screenName, - bio: profile.bio, - nicknames: profile.nicknames, - }, + this.runtime.character.twitterProfile = { + id: this.profile.id, + username: this.profile.username, + screenName: this.profile.screenName, + bio: this.profile.bio, + nicknames: this.profile.nicknames, }; } else { throw new Error("Failed to load profile"); } + await this.loadLatestCheckedTweetId(); await this.populateTimeline(); } @@ -245,7 +226,7 @@ export class ClientBase extends EventEmitter { return homeTimeline .filter((t) => t.__typename !== "TweetWithVisibilityResults") .map((tweet) => { - console.log("tweet is", tweet); + // console.log("tweet is", tweet); const obj = { id: tweet.rest_id, name: @@ -281,9 +262,7 @@ export class ClientBase extends EventEmitter { ) ?? [], }; - - console.log("obj is", obj); - + // console.log("obj is", obj); return obj; }); } @@ -316,11 +295,11 @@ export class ClientBase extends EventEmitter { ); return (result ?? { tweets: [] }) as QueryTweetsResponse; } catch (error) { - console.error("Error fetching search tweets:", error); + elizaLogger.error("Error fetching search tweets:", error); return { tweets: [] }; } } catch (error) { - console.error("Error fetching search tweets:", error); + elizaLogger.error("Error fetching search tweets:", error); return { tweets: [] }; } } @@ -370,7 +349,7 @@ export class ClientBase extends EventEmitter { tweet.conversationId + "-" + this.runtime.agentId ); const tweetuserId = - tweet.userId === this.twitterUserId + tweet.userId === this.profile.id ? this.runtime.agentId : stringToUuid(tweet.userId); @@ -482,7 +461,7 @@ export class ClientBase extends EventEmitter { tweet.conversationId + "-" + this.runtime.agentId ); const tweetuserId = - tweet.userId === this.twitterUserId + tweet.userId === this.profile.id ? this.runtime.agentId : stringToUuid(tweet.userId); @@ -560,10 +539,10 @@ export class ClientBase extends EventEmitter { } } - async loadCachedLatestCheckedTweetId(): Promise { + async loadLatestCheckedTweetId(): Promise { const latestCheckedTweetId = await this.runtime.cacheManager.get( - `twitter/${this.runtime.getSetting("TWITTER_USERNAME")}/latest_checked_tweet_id` + `twitter/${this.profile.username}/latest_checked_tweet_id` ); if (latestCheckedTweetId) { @@ -574,7 +553,7 @@ export class ClientBase extends EventEmitter { async cacheLatestCheckedTweetId() { if (this.lastCheckedTweetId) { await this.runtime.cacheManager.set( - `twitter/${this.runtime.getSetting("TWITTER_USERNAME")}/latest_checked_tweet_id`, + `twitter/${this.profile.username}/latest_checked_tweet_id`, this.lastCheckedTweetId ); } @@ -582,36 +561,31 @@ export class ClientBase extends EventEmitter { async getCachedTimeline(): Promise { return await this.runtime.cacheManager.get( - `twitter/${this.runtime.getSetting("TWITTER_USERNAME")}/timeline` + `twitter/${this.profile.username}/timeline` ); } async cacheTimeline(timeline: Tweet[]) { await this.runtime.cacheManager.set( - `twitter/${this.runtime.getSetting("TWITTER_USERNAME")}/timeline`, + `twitter/${this.profile.username}/timeline`, timeline ); } - async getCachedCookies() { + async getCachedCookies(username: string) { return await this.runtime.cacheManager.get( - `twitter/${this.runtime.getSetting("TWITTER_USERNAME")}/cookies` + `twitter/${username}/cookies` ); } - async cacheCookies(cookies: any[]) { + + async cacheCookies(username: string, cookies: any[]) { await this.runtime.cacheManager.set( - `twitter/${this.runtime.getSetting("TWITTER_USERNAME")}/cookies`, + `twitter/${username}/cookies`, cookies ); } - async loadProfile(): Promise { - const username = this.runtime.getSetting("TWITTER_USERNAME"); - if (!username) { - console.error("Twitter username not configured"); - return; - } - + async fetchProfile(username: string): Promise { const cached = await this.runtime.cacheManager.get( `twitter/${username}/profile` ); @@ -621,7 +595,9 @@ export class ClientBase extends EventEmitter { try { const profile = await this.requestQueue.add(async () => { const profile = await this.twitterClient.getProfile(username); + // console.log({ profile }); return { + id: profile.userId, username, screenName: profile.name || this.runtime.character.name, bio: @@ -633,7 +609,7 @@ export class ClientBase extends EventEmitter { : "", nicknames: this.runtime.character.twitterProfile?.nicknames || [], - }; + } satisfies TwitterProfile; }); this.runtime.cacheManager.set( @@ -644,18 +620,8 @@ export class ClientBase extends EventEmitter { return profile; } catch (error) { console.error("Error fetching Twitter profile:", error); - return { - username: this.runtime.character.name, - screenName: username, - bio: - typeof this.runtime.character.bio === "string" - ? (this.runtime.character.bio as string) - : this.runtime.character.bio.length > 0 - ? this.runtime.character.bio[0] - : "", - nicknames: - this.runtime.character.twitterProfile?.nicknames || [], - }; + + return undefined; } } } diff --git a/packages/client-twitter/src/interactions.ts b/packages/client-twitter/src/interactions.ts index 60d1f78b4c1..e94750c13ca 100644 --- a/packages/client-twitter/src/interactions.ts +++ b/packages/client-twitter/src/interactions.ts @@ -105,7 +105,7 @@ export class TwitterInteractionClient { async handleTwitterInteractions() { elizaLogger.log("Checking Twitter interactions"); - const twitterUsername = this.runtime.getSetting("TWITTER_USERNAME"); + const twitterUsername = this.client.profile.username; try { // Check for mentions const tweetCandidates = ( @@ -121,7 +121,7 @@ export class TwitterInteractionClient { // Sort tweet candidates by ID in ascending order uniqueTweetCandidates .sort((a, b) => a.id.localeCompare(b.id)) - .filter((tweet) => tweet.userId !== this.client.twitterUserId); + .filter((tweet) => tweet.userId !== this.client.profile.id); // for each tweet candidate, handle the tweet for (const tweet of uniqueTweetCandidates) { @@ -136,7 +136,7 @@ export class TwitterInteractionClient { ); const userIdUUID = - tweet.userId === this.client.twitterUserId + tweet.userId === this.client.profile.id ? this.runtime.agentId : stringToUuid(tweet.userId!); @@ -189,17 +189,18 @@ export class TwitterInteractionClient { message: Memory; thread: Tweet[]; }) { - if (tweet.username === this.runtime.getSetting("TWITTER_USERNAME")) { + if (tweet.userId === this.client.profile.id) { // console.log("skipping tweet from bot itself", tweet.id); // Skip processing if the tweet is from the bot itself return; } if (!message.content.text) { - elizaLogger.log("skipping tweet with no text", tweet.id); + elizaLogger.log("Skipping Tweet with no text", tweet.id); return { text: "", action: "IGNORE" }; } - elizaLogger.log("handling tweet", tweet.id); + + elizaLogger.log("Processing Tweet: ", tweet.id); const formatTweet = (tweet: Tweet) => { return ` ID: ${tweet.id} From: ${tweet.name} (@${tweet.username}) diff --git a/packages/core/src/runtime.ts b/packages/core/src/runtime.ts index 49feae66580..419883a3d23 100644 --- a/packages/core/src/runtime.ts +++ b/packages/core/src/runtime.ts @@ -631,9 +631,15 @@ export class AgentRuntime implements IAgentRuntime { await this.databaseAdapter.getParticipantsForRoom(roomId); if (!participants.includes(userId)) { await this.databaseAdapter.addParticipant(userId, roomId); - elizaLogger.log( - `User ${userId} linked to room ${roomId} successfully.` - ); + if (userId === this.agentId) { + elizaLogger.log( + `Agent ${this.character.name} linked to room ${roomId} successfully.` + ); + } else { + elizaLogger.log( + `User ${userId} linked to room ${roomId} successfully.` + ); + } } }