From 20f2d9d3637f834853351043252d3fa13e1eccf7 Mon Sep 17 00:00:00 2001 From: oxSaturn Date: Sat, 28 Dec 2024 14:19:03 +0800 Subject: [PATCH 01/18] fix: handle long tweet in utils --- packages/client-twitter/src/utils.ts | 29 ++++++++++++++-------------- 1 file changed, 14 insertions(+), 15 deletions(-) diff --git a/packages/client-twitter/src/utils.ts b/packages/client-twitter/src/utils.ts index 0e7db5d9868..b4058145923 100644 --- a/packages/client-twitter/src/utils.ts +++ b/packages/client-twitter/src/utils.ts @@ -172,11 +172,10 @@ export async function sendTweet( twitterUsername: string, inReplyTo: string ): Promise { - const tweetChunks = splitTweetContent( - content.text, - Number(client.runtime.getSetting("MAX_TWEET_LENGTH")) || - DEFAULT_MAX_TWEET_LENGTH - ); + const maxTweetLength = parseInt(client.runtime.getSetting("MAX_TWEET_LENGTH") ?? DEFAULT_MAX_TWEET_LENGTH.toString()); + const isLongTweet = maxTweetLength > 280; + + const tweetChunks = splitTweetContent(content.text, maxTweetLength); const sentTweets: Tweet[] = []; let previousTweetId = inReplyTo; @@ -214,20 +213,20 @@ export async function sendTweet( }) ); } - const result = await client.requestQueue.add( - async () => - await client.twitterClient.sendTweet( - chunk.trim(), - previousTweetId, - mediaData - ) + const result = await client.requestQueue.add(async () => + isLongTweet + ? client.twitterClient.sendLongTweet(chunk.trim(), previousTweetId, mediaData) + : client.twitterClient.sendTweet(chunk.trim(), previousTweetId, mediaData) ); + const body = await result.json(); + const tweetResult = isLongTweet + ? body.data.notetweet_create.tweet_results.result + : body.data.create_tweet.tweet_results.result; // if we have a response - if (body?.data?.create_tweet?.tweet_results?.result) { + if (tweetResult) { // Parse the response - const tweetResult = body.data.create_tweet.tweet_results.result; const finalTweet: Tweet = { id: tweetResult.rest_id, text: tweetResult.legacy.full_text, @@ -247,7 +246,7 @@ export async function sendTweet( sentTweets.push(finalTweet); previousTweetId = finalTweet.id; } else { - console.error("Error sending chunk", chunk, "response:", body); + elizaLogger.error("Error sending tweet chunk:", { chunk, response: body }); } // Wait a bit between tweets to avoid rate limiting issues From 6d19903eefaa6aba3184a53f7dc8b0c92fa1d431 Mon Sep 17 00:00:00 2001 From: TMs Date: Sat, 28 Dec 2024 14:37:18 +0800 Subject: [PATCH 02/18] feat: use OPENAI_API_URL from env to support custom OpenAI API endpoint --- .env.example | 1 + packages/core/src/embedding.ts | 2 +- packages/core/src/models.ts | 2 +- 3 files changed, 3 insertions(+), 2 deletions(-) diff --git a/.env.example b/.env.example index 1c171bffdb0..3740229b29a 100644 --- a/.env.example +++ b/.env.example @@ -9,6 +9,7 @@ DISCORD_VOICE_CHANNEL_ID= # The ID of the voice channel the bot should joi # AI Model API Keys OPENAI_API_KEY= # OpenAI API key, starting with sk- +OPENAI_API_URL= # OpenAI API Endpoint (optional), Default: https://api.openai.com/v1 SMALL_OPENAI_MODEL= # Default: gpt-4o-mini MEDIUM_OPENAI_MODEL= # Default: gpt-4o LARGE_OPENAI_MODEL= # Default: gpt-4o diff --git a/packages/core/src/embedding.ts b/packages/core/src/embedding.ts index 1c2a72c5b88..dc51432387f 100644 --- a/packages/core/src/embedding.ts +++ b/packages/core/src/embedding.ts @@ -189,7 +189,7 @@ export async function embed(runtime: IAgentRuntime, input: string) { if (config.provider === EmbeddingProvider.OpenAI) { return await getRemoteEmbedding(input, { model: config.model, - endpoint: "https://api.openai.com/v1", + endpoint: settings.OPENAI_API_URL || "https://api.openai.com/v1", apiKey: settings.OPENAI_API_KEY, dimensions: config.dimensions, }); diff --git a/packages/core/src/models.ts b/packages/core/src/models.ts index 53d3b8be244..7fee142fb49 100644 --- a/packages/core/src/models.ts +++ b/packages/core/src/models.ts @@ -3,7 +3,7 @@ import { Models, ModelProviderName, ModelClass } from "./types.ts"; export const models: Models = { [ModelProviderName.OPENAI]: { - endpoint: "https://api.openai.com/v1", + endpoint: settings.OPENAI_API_URL || "https://api.openai.com/v1", settings: { stop: [], maxInputTokens: 128000, From 39fed596a01c6f2f08858c531b21d98aedce620a Mon Sep 17 00:00:00 2001 From: odilitime Date: Sat, 28 Dec 2024 00:50:30 +0000 Subject: [PATCH 03/18] isFalsish() for parsing environment in a standard way --- packages/core/src/parsing.ts | 25 +++++++++++++++++++++++++ 1 file changed, 25 insertions(+) diff --git a/packages/core/src/parsing.ts b/packages/core/src/parsing.ts index 107ce8ea0bd..bec9e1a0ec0 100644 --- a/packages/core/src/parsing.ts +++ b/packages/core/src/parsing.ts @@ -205,3 +205,28 @@ export const parseActionResponseFromText = ( return { actions }; }; + +export const isFalsish = (input: any): boolean => { + // If the input is exactly NaN, return true + if (Number.isNaN(input)) { + return true; + } + + // Convert input to a string if it's not null or undefined + const value = input == null ? "" : String(input); + + // List of common falsish string representations + const falsishValues = [ + "false", + "0", + "no", + "n", + "off", + "null", + "undefined", + "", + ]; + + // Check if the value (trimmed and lowercased) is in the falsish list + return falsishValues.includes(value.trim().toLowerCase()); +} \ No newline at end of file From e7f96937937034b9e06e402b7adf0eedf56c6782 Mon Sep 17 00:00:00 2001 From: odilitime Date: Sat, 28 Dec 2024 01:25:14 +0000 Subject: [PATCH 04/18] roll isFalsish into parseBooleanFromText --- packages/core/src/parsing.ts | 30 +++--------------------------- 1 file changed, 3 insertions(+), 27 deletions(-) diff --git a/packages/core/src/parsing.ts b/packages/core/src/parsing.ts index bec9e1a0ec0..65e358cdd6c 100644 --- a/packages/core/src/parsing.ts +++ b/packages/core/src/parsing.ts @@ -44,10 +44,11 @@ export const booleanFooter = `Respond with only a YES or a NO.`; * @returns {boolean|null} - Returns `true` for affirmative inputs, `false` for negative inputs, and `null` for unrecognized inputs or null/undefined. */ export const parseBooleanFromText = (text: string) => { + // "NULL", "UNDEFINED" if (!text) return null; // Handle null or undefined input - const affirmative = ["YES", "Y", "TRUE", "T", "1", "ON", "ENABLE"]; - const negative = ["NO", "N", "FALSE", "F", "0", "OFF", "DISABLE"]; + const affirmative = ["YES", "Y", "TRUE", "T", "1", "ON", "ENABLE"]; + const negative = ["NO", "N", "FALSE", "F", "0", "OFF", "DISABLE", ""]; const normalizedText = text.trim().toUpperCase(); @@ -205,28 +206,3 @@ export const parseActionResponseFromText = ( return { actions }; }; - -export const isFalsish = (input: any): boolean => { - // If the input is exactly NaN, return true - if (Number.isNaN(input)) { - return true; - } - - // Convert input to a string if it's not null or undefined - const value = input == null ? "" : String(input); - - // List of common falsish string representations - const falsishValues = [ - "false", - "0", - "no", - "n", - "off", - "null", - "undefined", - "", - ]; - - // Check if the value (trimmed and lowercased) is in the falsish list - return falsishValues.includes(value.trim().toLowerCase()); -} \ No newline at end of file From d22369cb90fd2ca71feb713d5d9c43b76a30683a Mon Sep 17 00:00:00 2001 From: odilitime Date: Sat, 28 Dec 2024 08:24:35 +0000 Subject: [PATCH 05/18] lint: remove unused error variable --- packages/client-github/src/index.ts | 7 ++----- 1 file changed, 2 insertions(+), 5 deletions(-) diff --git a/packages/client-github/src/index.ts b/packages/client-github/src/index.ts index 92cf3540436..0d68dcb3bd7 100644 --- a/packages/client-github/src/index.ts +++ b/packages/client-github/src/index.ts @@ -82,11 +82,8 @@ export class GitHubClient { `Successfully cloned repository from ${repositoryUrl}` ); return; - } catch (error) { - elizaLogger.error( - `Failed to clone repository from ${repositoryUrl}. Retrying...`, - error - ); + } catch { + elizaLogger.error(`Failed to clone repository from ${repositoryUrl}. Retrying...`); retries++; if (retries === maxRetries) { throw new Error( From a2853fa0502afc03d5c40bd6161d05030e3819b3 Mon Sep 17 00:00:00 2001 From: odilitime Date: Sat, 28 Dec 2024 08:25:26 +0000 Subject: [PATCH 06/18] lint: fix EmbeddingProvider already defined error (via claude) --- packages/core/src/embedding.ts | 13 ++++++++++--- 1 file changed, 10 insertions(+), 3 deletions(-) diff --git a/packages/core/src/embedding.ts b/packages/core/src/embedding.ts index dc51432387f..7c4b2e0760a 100644 --- a/packages/core/src/embedding.ts +++ b/packages/core/src/embedding.ts @@ -14,15 +14,22 @@ interface EmbeddingOptions { provider?: string; } -export const EmbeddingProvider = { +// Define the providers as a const object +export const EMBEDDING_PROVIDERS = { OpenAI: "OpenAI", Ollama: "Ollama", GaiaNet: "GaiaNet", BGE: "BGE", } as const; -export type EmbeddingProviderType = - (typeof EmbeddingProvider)[keyof typeof EmbeddingProvider]; +// Create type from the values +export type EmbeddingProvider = typeof EMBEDDING_PROVIDERS[keyof typeof EMBEDDING_PROVIDERS]; + +// If you need individual types, use type aliases instead of namespace +export type OpenAIProvider = typeof EMBEDDING_PROVIDERS.OpenAI; +export type OllamaProvider = typeof EMBEDDING_PROVIDERS.Ollama; +export type GaiaNetProvider = typeof EMBEDDING_PROVIDERS.GaiaNet; +export type BGEProvider = typeof EMBEDDING_PROVIDERS.BGE; export type EmbeddingConfig = { readonly dimensions: number; From 35d26c6696820699e933a563c4edbb1e46851915 Mon Sep 17 00:00:00 2001 From: odilitime Date: Sat, 28 Dec 2024 02:07:25 +0000 Subject: [PATCH 07/18] empty is already mapped to null --- packages/core/src/parsing.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/packages/core/src/parsing.ts b/packages/core/src/parsing.ts index 65e358cdd6c..fba63712f20 100644 --- a/packages/core/src/parsing.ts +++ b/packages/core/src/parsing.ts @@ -48,7 +48,7 @@ export const parseBooleanFromText = (text: string) => { if (!text) return null; // Handle null or undefined input const affirmative = ["YES", "Y", "TRUE", "T", "1", "ON", "ENABLE"]; - const negative = ["NO", "N", "FALSE", "F", "0", "OFF", "DISABLE", ""]; + const negative = ["NO", "N", "FALSE", "F", "0", "OFF", "DISABLE"]; const normalizedText = text.trim().toUpperCase(); From bdabfbc8e65471c76a0cf75c9d71fc395ad75971 Mon Sep 17 00:00:00 2001 From: odilitime Date: Sat, 28 Dec 2024 02:08:14 +0000 Subject: [PATCH 08/18] move all env parsing here --- packages/client-twitter/src/environment.ts | 139 +++++++++++++++++++-- 1 file changed, 128 insertions(+), 11 deletions(-) diff --git a/packages/client-twitter/src/environment.ts b/packages/client-twitter/src/environment.ts index a5a5bbf82e8..80e47338fcd 100644 --- a/packages/client-twitter/src/environment.ts +++ b/packages/client-twitter/src/environment.ts @@ -1,34 +1,103 @@ -import { IAgentRuntime } from "@elizaos/core"; +import { parseBooleanFromText, IAgentRuntime } from "@elizaos/core"; import { z } from "zod"; - export const DEFAULT_MAX_TWEET_LENGTH = 280; +const twitterUsernameSchema = z.string() + .min(1) + .max(15) + .regex(/^[A-Za-z][A-Za-z0-9_]*[A-Za-z0-9]$|^[A-Za-z]$/, 'Invalid Twitter username format'); + export const twitterEnvSchema = z.object({ - TWITTER_DRY_RUN: z - .string() - .transform((val) => val.toLowerCase() === "true"), + TWITTER_DRY_RUN: z.boolean(), TWITTER_USERNAME: z.string().min(1, "Twitter username is required"), TWITTER_PASSWORD: z.string().min(1, "Twitter password is required"), TWITTER_EMAIL: z.string().email("Valid Twitter email is required"), MAX_TWEET_LENGTH: z .string() - .pipe(z.coerce.number().min(0).int()) + .pipe(z.coerce.number().min(1).int()) .default(DEFAULT_MAX_TWEET_LENGTH.toString()), + TWITTER_SEARCH_ENABLE: z.boolean().default(false), + TWITTER_2FA_SECRET: z.string(), + TWITTER_RETRY_LIMIT: z.number().int(), + TWITTER_POLL_INTERVAL: z.number().int(), + TWITTER_TARGET_USERS: z.array(twitterUsernameSchema).default([]), + // I guess it's possible to do the transformation with zod + // not sure it's preferable, maybe a readability issue + // since more people will know js/ts than zod + /* + z + .string() + .transform((val) => val.trim()) + .pipe( + z.string() + .transform((val) => + val ? val.split(',').map((u) => u.trim()).filter(Boolean) : [] + ) + .pipe( + z.array( + z.string() + .min(1) + .max(15) + .regex( + /^[A-Za-z][A-Za-z0-9_]*[A-Za-z0-9]$|^[A-Za-z]$/, + 'Invalid Twitter username format' + ) + ) + ) + .transform((users) => users.join(',')) + ) + .optional() + .default(''), + */ + POST_INTERVAL_MIN: z.number().int(), + POST_INTERVAL_MAX: z.number().int(), + ENABLE_ACTION_PROCESSING: z.boolean(), + ACTION_INTERVAL: z.number().int(), + POST_IMMEDIATELY: z.boolean(), }); export type TwitterConfig = z.infer; +function parseTargetUsers(targetUsersStr?:string | null): string[] { + if (!targetUsersStr?.trim()) { + return []; + } + + return targetUsersStr + .split(',') + .map(user => user.trim()) + .filter(Boolean); // Remove empty usernames + /* + .filter(user => { + // Twitter username validation (basic example) + return user && /^[A-Za-z0-9_]{1,15}$/.test(user); + }); + */ +} + +function safeParseInt(value: string | undefined | null, defaultValue: number): number { + if (!value) return defaultValue; + const parsed = parseInt(value, 10); + return isNaN(parsed) ? defaultValue : Math.max(1, parsed); +} + +// This also is organized to serve as a point of documentation for the client +// most of the inputs from the framework (env/character) + +// we also do a lot of typing/parsing here +// so we can do it once and only once per character export async function validateTwitterConfig( runtime: IAgentRuntime ): Promise { try { const twitterConfig = { TWITTER_DRY_RUN: - runtime.getSetting("TWITTER_DRY_RUN") || - process.env.TWITTER_DRY_RUN || - "false", + parseBooleanFromText( + runtime.getSetting("TWITTER_DRY_RUN") || + process.env.TWITTER_DRY_RUN + ) ?? false, // parseBooleanFromText return null if "", map "" to false TWITTER_USERNAME: - runtime.getSetting("TWITTER_USERNAME") || + runtime.getSetting ("TWITTER_USERNAME") || process.env.TWITTER_USERNAME, TWITTER_PASSWORD: runtime.getSetting("TWITTER_PASSWORD") || @@ -36,10 +105,58 @@ export async function validateTwitterConfig( TWITTER_EMAIL: runtime.getSetting("TWITTER_EMAIL") || process.env.TWITTER_EMAIL, - MAX_TWEET_LENGTH: + MAX_TWEET_LENGTH: // number as string? runtime.getSetting("MAX_TWEET_LENGTH") || process.env.MAX_TWEET_LENGTH || DEFAULT_MAX_TWEET_LENGTH.toString(), + TWITTER_SEARCH_ENABLE: // bool + parseBooleanFromText( + runtime.getSetting("TWITTER_SEARCH_ENABLE") || + process.env.TWITTER_SEARCH_ENABLE + ) ?? false, + TWITTER_2FA_SECRET: // string passthru + runtime.getSetting("TWITTER_2FA_SECRET") || + process.env.TWITTER_2FA_SECRET || "", + TWITTER_RETRY_LIMIT: // int + safeParseInt( + runtime.getSetting("TWITTER_RETRY_LIMIT") || + process.env.TWITTER_RETRY_LIMIT + , 5), + TWITTER_POLL_INTERVAL: // int in minutes + safeParseInt( + runtime.getSetting("TWITTER_POLL_INTERVAL") || + process.env.TWITTER_POLL_INTERVAL + , 120), // 2h + TWITTER_TARGET_USERS: // comma separated string + parseTargetUsers( + runtime.getSetting("TWITTER_TARGET_USERS") || + process.env.TWITTER_TARGET_USERS + ), + POST_INTERVAL_MIN: // int in minutes + safeParseInt( + runtime.getSetting("POST_INTERVAL_MIN") || + process.env.POST_INTERVAL_MIN + , 90), // 1.5 hours + POST_INTERVAL_MAX: // int in minutes + safeParseInt( + runtime.getSetting("POST_INTERVAL_MAX") || + process.env.POST_INTERVAL_MAX + , 180), // 3 hours + ENABLE_ACTION_PROCESSING: // bool + parseBooleanFromText( + runtime.getSetting("ENABLE_ACTION_PROCESSING") || + process.env.ENABLE_ACTION_PROCESSING + ) ?? false, + ACTION_INTERVAL: // int in minutes (min 1m) + safeParseInt( + runtime.getSetting("ACTION_INTERVAL") || + process.env.ACTION_INTERVAL + , 5), // 5 minutes + POST_IMMEDIATELY: // bool + parseBooleanFromText( + runtime.getSetting("POST_IMMEDIATELY") || + process.env.POST_IMMEDIATELY + ) ?? false, }; return twitterEnvSchema.parse(twitterConfig); From cad36a7c93b5a2bb13e4bb2b187a9868bfb3751e Mon Sep 17 00:00:00 2001 From: odilitime Date: Sat, 28 Dec 2024 02:08:46 +0000 Subject: [PATCH 09/18] add twitterConfig cstr param and prefer it over getSetting --- packages/client-twitter/src/base.ts | 25 ++++++++++++------------- 1 file changed, 12 insertions(+), 13 deletions(-) diff --git a/packages/client-twitter/src/base.ts b/packages/client-twitter/src/base.ts index 1b04485d6d2..ed4f8481496 100644 --- a/packages/client-twitter/src/base.ts +++ b/packages/client-twitter/src/base.ts @@ -16,6 +16,7 @@ import { Tweet, } from "agent-twitter-client"; import { EventEmitter } from "events"; +import { TwitterConfig } from "./environment.ts"; export function extractAnswer(text: string): string { const startIndex = text.indexOf("Answer: ") + 8; @@ -85,6 +86,7 @@ export class ClientBase extends EventEmitter { static _twitterClients: { [accountIdentifier: string]: Scraper } = {}; twitterClient: Scraper; runtime: IAgentRuntime; + twitterConfig: TwitterConfig; directions: string; lastCheckedTweetId: bigint | null = null; imageDescriptionService: IImageDescriptionService; @@ -134,10 +136,11 @@ export class ClientBase extends EventEmitter { ); } - constructor(runtime: IAgentRuntime) { + constructor(runtime: IAgentRuntime, twitterConfig:TwitterConfig) { super(); this.runtime = runtime; - const username = this.runtime.getSetting("TWITTER_USERNAME"); + this.twitterConfig = twitterConfig; + const username = twitterConfig.TWITTER_USERNAME; if (ClientBase._twitterClients[username]) { this.twitterClient = ClientBase._twitterClients[username]; } else { @@ -153,15 +156,11 @@ export class ClientBase extends EventEmitter { } async init() { - const username = this.runtime.getSetting("TWITTER_USERNAME"); - const password = this.runtime.getSetting("TWITTER_PASSWORD"); - const email = this.runtime.getSetting("TWITTER_EMAIL"); - let retries = parseInt( - this.runtime.getSetting("TWITTER_RETRY_LIMIT") || "5", - 10 - ); - const twitter2faSecret = - this.runtime.getSetting("TWITTER_2FA_SECRET") || undefined; + const username = this.twitterConfig.TWITTER_USERNAME; + const password = this.twitterConfig.TWITTER_PASSWORD; + const email = this.twitterConfig.TWITTER_EMAIL; + let retries = this.twitterConfig.TWITTER_RETRY_LIMIT + const twitter2faSecret = this.twitterConfig.TWITTER_2FA_SECRET; if (!username) { throw new Error("Twitter username not configured"); @@ -314,7 +313,7 @@ export class ClientBase extends EventEmitter { async fetchTimelineForActions(count: number): Promise { elizaLogger.debug("fetching timeline for actions"); - const agentUsername = this.runtime.getSetting("TWITTER_USERNAME"); + const agentUsername = this.twitterConfig.TWITTER_USERNAME const homeTimeline = await this.twitterClient.fetchHomeTimeline( count, [] @@ -510,7 +509,7 @@ export class ClientBase extends EventEmitter { } const timeline = await this.fetchHomeTimeline(cachedTimeline ? 10 : 50); - const username = this.runtime.getSetting("TWITTER_USERNAME"); + const username = this.twitterConfig.TWITTER_USERNAME; // Get the most recent 20 mentions and interactions const mentionsAndInteractions = await this.fetchSearchTweets( From bd78337728c1c2dc1fd2b8cf29d4e2f59a120fba Mon Sep 17 00:00:00 2001 From: odilitime Date: Sat, 28 Dec 2024 02:09:08 +0000 Subject: [PATCH 10/18] prefer this.client.twitterConfig over getSetting --- packages/client-twitter/src/interactions.ts | 32 ++++++--------------- 1 file changed, 8 insertions(+), 24 deletions(-) diff --git a/packages/client-twitter/src/interactions.ts b/packages/client-twitter/src/interactions.ts index 438445ecbeb..3274cd32308 100644 --- a/packages/client-twitter/src/interactions.ts +++ b/packages/client-twitter/src/interactions.ts @@ -100,9 +100,8 @@ export class TwitterInteractionClient { this.handleTwitterInteractions(); setTimeout( handleTwitterInteractionsLoop, - Number( - this.runtime.getSetting("TWITTER_POLL_INTERVAL") || 120 - ) * 1000 // Default to 2 minutes + // Defaults to 2 minutes + this.client.twitterConfig.TWITTER_POLL_INTERVAL * 1000 ); }; handleTwitterInteractionsLoop(); @@ -110,8 +109,6 @@ export class TwitterInteractionClient { async handleTwitterInteractions() { elizaLogger.log("Checking Twitter interactions"); - // Read from environment variable, fallback to default list if not set - const targetUsersStr = this.runtime.getSetting("TWITTER_TARGET_USERS"); const twitterUsername = this.client.profile.username; try { @@ -130,11 +127,8 @@ export class TwitterInteractionClient { ); let uniqueTweetCandidates = [...mentionCandidates]; // Only process target users if configured - if (targetUsersStr && targetUsersStr.trim()) { - const TARGET_USERS = targetUsersStr - .split(",") - .map((u) => u.trim()) - .filter((u) => u.length > 0); // Filter out empty strings after split + if (this.client.twitterConfig.TWITTER_TARGET_USERS.length) { + const TARGET_USERS = this.client.twitterConfig.TWITTER_TARGET_USERS; elizaLogger.log("Processing target users:", TARGET_USERS); @@ -347,7 +341,7 @@ export class TwitterInteractionClient { let state = await this.runtime.composeState(message, { twitterClient: this.client.twitterClient, - twitterUserName: this.runtime.getSetting("TWITTER_USERNAME"), + twitterUserName: this.client.twitterConfig.TWITTER_USERNAME, currentPost, formattedConversation, }); @@ -383,18 +377,8 @@ export class TwitterInteractionClient { this.client.saveRequestMessage(message, state); } - // 1. Get the raw target users string from settings - const targetUsersStr = this.runtime.getSetting("TWITTER_TARGET_USERS"); - - // 2. Process the string to get valid usernames - const validTargetUsersStr = - targetUsersStr && targetUsersStr.trim() - ? targetUsersStr - .split(",") // Split by commas: "user1,user2" -> ["user1", "user2"] - .map((u) => u.trim()) // Remove whitespace: [" user1 ", "user2 "] -> ["user1", "user2"] - .filter((u) => u.length > 0) - .join(",") - : ""; + // get usernames into str + const validTargetUsersStr = this.client.twitterConfig.TWITTER_TARGET_USERS.join(","); const shouldRespondContext = composeContext({ state, @@ -450,7 +434,7 @@ export class TwitterInteractionClient { this.client, response, message.roomId, - this.runtime.getSetting("TWITTER_USERNAME"), + this.client.twitterConfig.TWITTER_USERNAME, tweet.id ); return memories; From ec33a178c2534f095f6ab399b71773c351c68750 Mon Sep 17 00:00:00 2001 From: odilitime Date: Sat, 28 Dec 2024 08:26:34 +0000 Subject: [PATCH 11/18] prefer client.twitterConfig over getSetting --- packages/client-twitter/src/utils.ts | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/packages/client-twitter/src/utils.ts b/packages/client-twitter/src/utils.ts index b4058145923..32ef6247dcc 100644 --- a/packages/client-twitter/src/utils.ts +++ b/packages/client-twitter/src/utils.ts @@ -4,7 +4,6 @@ import { Content, Memory, UUID } from "@elizaos/core"; import { stringToUuid } from "@elizaos/core"; import { ClientBase } from "./base"; import { elizaLogger } from "@elizaos/core"; -import { DEFAULT_MAX_TWEET_LENGTH } from "./environment"; import { Media } from "@elizaos/core"; import fs from "fs"; import path from "path"; @@ -172,7 +171,7 @@ export async function sendTweet( twitterUsername: string, inReplyTo: string ): Promise { - const maxTweetLength = parseInt(client.runtime.getSetting("MAX_TWEET_LENGTH") ?? DEFAULT_MAX_TWEET_LENGTH.toString()); + const maxTweetLength = client.twitterConfig.MAX_TWEET_LENGTH; const isLongTweet = maxTweetLength > 280; const tweetChunks = splitTweetContent(content.text, maxTweetLength); From b8bde37783765e257455798ac2a21f6dd10d851b Mon Sep 17 00:00:00 2001 From: odilitime Date: Sat, 28 Dec 2024 02:10:21 +0000 Subject: [PATCH 12/18] prefer this.client.twitterConfig over getSetting --- packages/client-twitter/src/search.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/packages/client-twitter/src/search.ts b/packages/client-twitter/src/search.ts index 8934abf72e3..37254ed1a7f 100644 --- a/packages/client-twitter/src/search.ts +++ b/packages/client-twitter/src/search.ts @@ -51,7 +51,7 @@ export class TwitterSearchClient { constructor(client: ClientBase, runtime: IAgentRuntime) { this.client = client; this.runtime = runtime; - this.twitterUsername = runtime.getSetting("TWITTER_USERNAME"); + this.twitterUsername = this.client.twitterConfig.TWITTER_USERNAME; } async start() { From 2c577326ffea610b062b3d11d41978875c0f3e1d Mon Sep 17 00:00:00 2001 From: odilitime Date: Sat, 28 Dec 2024 02:10:50 +0000 Subject: [PATCH 13/18] prefer this.client.twitterConfig over getSettings --- packages/client-twitter/src/post.ts | 60 +++++++++-------------------- 1 file changed, 18 insertions(+), 42 deletions(-) diff --git a/packages/client-twitter/src/post.ts b/packages/client-twitter/src/post.ts index cd7b8b9d6ec..41466c5ba3d 100644 --- a/packages/client-twitter/src/post.ts +++ b/packages/client-twitter/src/post.ts @@ -6,7 +6,6 @@ import { IAgentRuntime, ModelClass, stringToUuid, - parseBooleanFromText, UUID, } from "@elizaos/core"; import { elizaLogger } from "@elizaos/core"; @@ -106,10 +105,8 @@ export class TwitterPostClient { constructor(client: ClientBase, runtime: IAgentRuntime) { this.client = client; this.runtime = runtime; - this.twitterUsername = runtime.getSetting("TWITTER_USERNAME"); - this.isDryRun = parseBooleanFromText( - runtime.getSetting("TWITTER_DRY_RUN") ?? "false" - ); + this.twitterUsername = this.client.twitterConfig.TWITTER_USERNAME; + this.isDryRun = this.client.twitterConfig.TWITTER_DRY_RUN // Log configuration on initialization elizaLogger.log("Twitter Client Configuration:"); @@ -118,34 +115,34 @@ export class TwitterPostClient { `- Dry Run Mode: ${this.isDryRun ? "enabled" : "disabled"}` ); elizaLogger.log( - `- Post Interval: ${runtime.getSetting("POST_INTERVAL_MIN") || "90"}-${runtime.getSetting("POST_INTERVAL_MAX") || "180"} minutes` + `- Post Interval: ${this.client.twitterConfig.POST_INTERVAL_MIN}-${this.client.twitterConfig.POST_INTERVAL_MAX} minutes` ); elizaLogger.log( - `- Action Processing: ${parseBooleanFromText(runtime.getSetting("ENABLE_ACTION_PROCESSING") ?? "false") ? "enabled" : "disabled"}` + `- Action Processing: ${this.client.twitterConfig.ENABLE_ACTION_PROCESSING ? "enabled" : "disabled"}` ); elizaLogger.log( - `- Action Interval: ${(parseInt(runtime.getSetting("ACTION_INTERVAL") ?? "300000") / 1000).toFixed(0)} seconds` + `- Action Interval: ${this.client.twitterConfig.ACTION_INTERVAL} seconds` ); elizaLogger.log( - `- Post Immediately: ${parseBooleanFromText(runtime.getSetting("POST_IMMEDIATELY") ?? "false") ? "enabled" : "disabled"}` + `- Post Immediately: ${this.client.twitterConfig.POST_IMMEDIATELY ? "enabled" : "disabled"}` ); elizaLogger.log( - `- Search Enabled: ${parseBooleanFromText(runtime.getSetting("TWITTER_SEARCH_ENABLE") ?? "false") ? "enabled" : "disabled"}` + `- Search Enabled: ${this.client.twitterConfig.TWITTER_SEARCH_ENABLE ? "enabled" : "disabled"}` ); - const targetUsers = runtime.getSetting("TWITTER_TARGET_USERS"); + const targetUsers = this.client.twitterConfig.TWITTER_TARGET_USERS; if (targetUsers) { elizaLogger.log(`- Target Users: ${targetUsers}`); } if (this.isDryRun) { elizaLogger.log( - "Twitter client initialized in dry run mode - no actual tweets will be posted" + "Twitter client initialized in dry run mode - no actual tweets should be posted" ); } } - async start(postImmediately: boolean = false) { + async start() { if (!this.client.profile) { await this.client.init(); } @@ -156,10 +153,8 @@ export class TwitterPostClient { }>("twitter/" + this.twitterUsername + "/lastPost"); const lastPostTimestamp = lastPost?.timestamp ?? 0; - const minMinutes = - parseInt(this.runtime.getSetting("POST_INTERVAL_MIN")) || 90; - const maxMinutes = - parseInt(this.runtime.getSetting("POST_INTERVAL_MAX")) || 180; + const minMinutes = this.client.twitterConfig.POST_INTERVAL_MIN; + const maxMinutes = this.client.twitterConfig.POST_INTERVAL_MAX; const randomMinutes = Math.floor(Math.random() * (maxMinutes - minMinutes + 1)) + minMinutes; @@ -177,8 +172,7 @@ export class TwitterPostClient { }; const processActionsLoop = async () => { - const actionInterval = - parseInt(this.runtime.getSetting("ACTION_INTERVAL")) || 300000; // Default to 5 minutes + const actionInterval = this.client.twitterConfig.ACTION_INTERVAL; // Defaults to 5 minutes while (!this.stopProcessingActions) { try { @@ -190,7 +184,7 @@ export class TwitterPostClient { ); // Wait for the full interval before next processing await new Promise((resolve) => - setTimeout(resolve, actionInterval) + setTimeout(resolve, actionInterval * 60 * 1000) // now in minutes ); } } catch (error) { @@ -204,16 +198,7 @@ export class TwitterPostClient { } }; - if ( - this.runtime.getSetting("POST_IMMEDIATELY") != null && - this.runtime.getSetting("POST_IMMEDIATELY") !== "" - ) { - // Retrieve setting, default to false if not set or if the value is not "true" - postImmediately = - this.runtime.getSetting("POST_IMMEDIATELY") === "true" || false; - } - - if (postImmediately) { + if (this.client.twitterConfig.POST_IMMEDIATELY) { await this.generateNewTweet(); } @@ -225,12 +210,7 @@ export class TwitterPostClient { elizaLogger.log("Tweet generation loop disabled (dry run mode)"); } - // Add check for ENABLE_ACTION_PROCESSING before starting the loop - const enableActionProcessing = parseBooleanFromText( - this.runtime.getSetting("ENABLE_ACTION_PROCESSING") ?? "false" - ); - - if (enableActionProcessing && !this.isDryRun) { + if (this.client.twitterConfig.ENABLE_ACTION_PROCESSING && !this.isDryRun) { processActionsLoop().catch((error) => { elizaLogger.error( "Fatal error in process actions loop:", @@ -333,8 +313,7 @@ export class TwitterPostClient { // Note Tweet failed due to authorization. Falling back to standard Tweet. const truncateContent = truncateToCompleteSentence( content, - parseInt(runtime.getSetting("MAX_TWEET_LENGTH")) || - DEFAULT_MAX_TWEET_LENGTH + this.client.twitterConfig.MAX_TWEET_LENGTH ); return await this.sendStandardTweet( client, @@ -496,10 +475,7 @@ export class TwitterPostClient { } // Truncate the content to the maximum tweet length specified in the environment settings, ensuring the truncation respects sentence boundaries. - const maxTweetLength = parseInt( - this.runtime.getSetting("MAX_TWEET_LENGTH"), - 10 - ); + const maxTweetLength = this.client.twitterConfig.MAX_TWEET_LENGTH if (maxTweetLength) { cleanedContent = truncateToCompleteSentence( cleanedContent, From 68288ad893005ea41630def416a8e0b96322a8b3 Mon Sep 17 00:00:00 2001 From: odilitime Date: Sat, 28 Dec 2024 02:11:37 +0000 Subject: [PATCH 14/18] pass twitterConfig as 2nd parameter to cstr, only start search once --- packages/client-twitter/src/index.ts | 14 ++++++-------- 1 file changed, 6 insertions(+), 8 deletions(-) diff --git a/packages/client-twitter/src/index.ts b/packages/client-twitter/src/index.ts index 3692525a240..0da22e7d6e3 100644 --- a/packages/client-twitter/src/index.ts +++ b/packages/client-twitter/src/index.ts @@ -1,6 +1,6 @@ import { Client, elizaLogger, IAgentRuntime } from "@elizaos/core"; import { ClientBase } from "./base.ts"; -import { validateTwitterConfig } from "./environment.ts"; +import { validateTwitterConfig, TwitterConfig } from "./environment.ts"; import { TwitterInteractionClient } from "./interactions.ts"; import { TwitterPostClient } from "./post.ts"; import { TwitterSearchClient } from "./search.ts"; @@ -10,11 +10,11 @@ class TwitterManager { post: TwitterPostClient; search: TwitterSearchClient; interaction: TwitterInteractionClient; - constructor(runtime: IAgentRuntime, enableSearch: boolean) { - this.client = new ClientBase(runtime); + constructor(runtime: IAgentRuntime, twitterConfig:TwitterConfig) { + this.client = new ClientBase(runtime, twitterConfig); this.post = new TwitterPostClient(this.client, runtime); - if (enableSearch) { + if (twitterConfig.TWITTER_SEARCH_ENABLE) { // this searches topics from character file elizaLogger.warn("Twitter/X client running in a mode that:"); elizaLogger.warn("1. violates consent of random users"); @@ -30,11 +30,11 @@ class TwitterManager { export const TwitterClientInterface: Client = { async start(runtime: IAgentRuntime) { - await validateTwitterConfig(runtime); + const twitterConfig:TwitterConfig = await validateTwitterConfig(runtime); elizaLogger.log("Twitter client started"); - const manager = new TwitterManager(runtime, runtime.getSetting("TWITTER_SEARCH_ENABLE").toLowerCase() === "true"); + const manager = new TwitterManager(runtime, twitterConfig); await manager.client.init(); @@ -45,8 +45,6 @@ export const TwitterClientInterface: Client = { await manager.interaction.start(); - await manager.search?.start(); - return manager; }, async stop(_runtime: IAgentRuntime) { From 4f92818a4fd57e8fdb44068902420ef20cfbb17f Mon Sep 17 00:00:00 2001 From: odilitime Date: Sat, 28 Dec 2024 02:32:23 +0000 Subject: [PATCH 15/18] fix TWITTER_POLL_INTERVAL scale --- packages/client-twitter/src/environment.ts | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/packages/client-twitter/src/environment.ts b/packages/client-twitter/src/environment.ts index 80e47338fcd..032714e3b74 100644 --- a/packages/client-twitter/src/environment.ts +++ b/packages/client-twitter/src/environment.ts @@ -122,11 +122,11 @@ export async function validateTwitterConfig( runtime.getSetting("TWITTER_RETRY_LIMIT") || process.env.TWITTER_RETRY_LIMIT , 5), - TWITTER_POLL_INTERVAL: // int in minutes + TWITTER_POLL_INTERVAL: // int in seconds safeParseInt( runtime.getSetting("TWITTER_POLL_INTERVAL") || process.env.TWITTER_POLL_INTERVAL - , 120), // 2h + , 120), // 2m TWITTER_TARGET_USERS: // comma separated string parseTargetUsers( runtime.getSetting("TWITTER_TARGET_USERS") || From 7fce277e8d820a8451216d5495825244f321f62b Mon Sep 17 00:00:00 2001 From: odilitime Date: Sat, 28 Dec 2024 02:41:50 +0000 Subject: [PATCH 16/18] make MAX_TWEET_LENGTH integer since that's how it's consomed/compared --- packages/client-twitter/src/environment.ts | 12 +++++------- 1 file changed, 5 insertions(+), 7 deletions(-) diff --git a/packages/client-twitter/src/environment.ts b/packages/client-twitter/src/environment.ts index 032714e3b74..8ff2fb454ed 100644 --- a/packages/client-twitter/src/environment.ts +++ b/packages/client-twitter/src/environment.ts @@ -12,10 +12,7 @@ export const twitterEnvSchema = z.object({ TWITTER_USERNAME: z.string().min(1, "Twitter username is required"), TWITTER_PASSWORD: z.string().min(1, "Twitter password is required"), TWITTER_EMAIL: z.string().email("Valid Twitter email is required"), - MAX_TWEET_LENGTH: z - .string() - .pipe(z.coerce.number().min(1).int()) - .default(DEFAULT_MAX_TWEET_LENGTH.toString()), + MAX_TWEET_LENGTH: z.number().int().default(DEFAULT_MAX_TWEET_LENGTH), TWITTER_SEARCH_ENABLE: z.boolean().default(false), TWITTER_2FA_SECRET: z.string(), TWITTER_RETRY_LIMIT: z.number().int(), @@ -106,9 +103,10 @@ export async function validateTwitterConfig( runtime.getSetting("TWITTER_EMAIL") || process.env.TWITTER_EMAIL, MAX_TWEET_LENGTH: // number as string? - runtime.getSetting("MAX_TWEET_LENGTH") || - process.env.MAX_TWEET_LENGTH || - DEFAULT_MAX_TWEET_LENGTH.toString(), + safeParseInt( + runtime.getSetting("MAX_TWEET_LENGTH") || + process.env.MAX_TWEET_LENGTH + , DEFAULT_MAX_TWEET_LENGTH), TWITTER_SEARCH_ENABLE: // bool parseBooleanFromText( runtime.getSetting("TWITTER_SEARCH_ENABLE") || From 8b9278e6eccb6fe6de95fb99904779f1ec15e0c4 Mon Sep 17 00:00:00 2001 From: Shakker Nerd Date: Sat, 28 Dec 2024 07:24:07 +0000 Subject: [PATCH 17/18] chore: revert file to match develop branch --- packages/core/src/embedding.ts | 13 +++---------- 1 file changed, 3 insertions(+), 10 deletions(-) diff --git a/packages/core/src/embedding.ts b/packages/core/src/embedding.ts index 7c4b2e0760a..dc51432387f 100644 --- a/packages/core/src/embedding.ts +++ b/packages/core/src/embedding.ts @@ -14,22 +14,15 @@ interface EmbeddingOptions { provider?: string; } -// Define the providers as a const object -export const EMBEDDING_PROVIDERS = { +export const EmbeddingProvider = { OpenAI: "OpenAI", Ollama: "Ollama", GaiaNet: "GaiaNet", BGE: "BGE", } as const; -// Create type from the values -export type EmbeddingProvider = typeof EMBEDDING_PROVIDERS[keyof typeof EMBEDDING_PROVIDERS]; - -// If you need individual types, use type aliases instead of namespace -export type OpenAIProvider = typeof EMBEDDING_PROVIDERS.OpenAI; -export type OllamaProvider = typeof EMBEDDING_PROVIDERS.Ollama; -export type GaiaNetProvider = typeof EMBEDDING_PROVIDERS.GaiaNet; -export type BGEProvider = typeof EMBEDDING_PROVIDERS.BGE; +export type EmbeddingProviderType = + (typeof EmbeddingProvider)[keyof typeof EmbeddingProvider]; export type EmbeddingConfig = { readonly dimensions: number; From d537154b3ee11d17bba55ca5aba258e727ada6e0 Mon Sep 17 00:00:00 2001 From: Shakker Nerd Date: Sat, 28 Dec 2024 07:24:52 +0000 Subject: [PATCH 18/18] chore: revert file to match develop branch --- packages/core/src/parsing.ts | 5 ++--- 1 file changed, 2 insertions(+), 3 deletions(-) diff --git a/packages/core/src/parsing.ts b/packages/core/src/parsing.ts index fba63712f20..107ce8ea0bd 100644 --- a/packages/core/src/parsing.ts +++ b/packages/core/src/parsing.ts @@ -44,11 +44,10 @@ export const booleanFooter = `Respond with only a YES or a NO.`; * @returns {boolean|null} - Returns `true` for affirmative inputs, `false` for negative inputs, and `null` for unrecognized inputs or null/undefined. */ export const parseBooleanFromText = (text: string) => { - // "NULL", "UNDEFINED" if (!text) return null; // Handle null or undefined input - const affirmative = ["YES", "Y", "TRUE", "T", "1", "ON", "ENABLE"]; - const negative = ["NO", "N", "FALSE", "F", "0", "OFF", "DISABLE"]; + const affirmative = ["YES", "Y", "TRUE", "T", "1", "ON", "ENABLE"]; + const negative = ["NO", "N", "FALSE", "F", "0", "OFF", "DISABLE"]; const normalizedText = text.trim().toUpperCase();