Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion src/clients/farcaster/interactions.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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
}

Expand Down
11 changes: 7 additions & 4 deletions src/clients/twitter/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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:
Expand All @@ -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)

Expand All @@ -38,11 +39,13 @@ class TwitterManager {

// Mentions and interactions
this.interaction = new TwitterInteractionClient(this.client, runtime)

elizaLogger.info('🐦 Twitter client initialized')
}
}

export const TwitterClientInterface: Client = {
async start(runtime: IAgentRuntime) {
async start(runtime: AgentcoinRuntime) {
const twitterConfig: TwitterConfig = await validateTwitterConfig(runtime)

elizaLogger.log('Twitter client started')
Expand All @@ -66,7 +69,7 @@ export const TwitterClientInterface: Client = {
return manager
},

async stop(_runtime: IAgentRuntime) {
async stop(_runtime: AgentcoinRuntime) {
elizaLogger.warn('Twitter client does not support stopping yet')
}
}
Expand Down
104 changes: 86 additions & 18 deletions src/clients/twitter/interactions.ts
Original file line number Diff line number Diff line change
@@ -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,
Expand All @@ -8,7 +10,6 @@ import {
generateShouldRespond,
getEmbeddingZeroVector,
type HandlerCallback,
type IAgentRuntime,
type IImageDescriptionService,
type Memory,
messageCompletionFooter,
Expand Down Expand Up @@ -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
Expand Down Expand Up @@ -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)

Expand Down Expand Up @@ -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)

Expand Down Expand Up @@ -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,
Expand All @@ -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(
Expand All @@ -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)
}
)
Expand Down
50 changes: 38 additions & 12 deletions src/clients/twitter/post.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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,
Expand All @@ -12,7 +13,6 @@ import {
generateText,
generateTweetActions,
getEmbeddingZeroVector,
type IAgentRuntime,
type IImageDescriptionService,
ModelClass,
parseJSONObjectFromText,
Expand Down Expand Up @@ -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
Expand All @@ -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
Expand Down Expand Up @@ -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')
Expand Down Expand Up @@ -297,7 +298,7 @@ export class TwitterPostClient {
}

async processAndCacheTweet(
runtime: IAgentRuntime,
runtime: AgentcoinRuntime,
client: ClientBase,
tweet: Tweet,
roomId: UUID,
Expand Down Expand Up @@ -403,7 +404,7 @@ export class TwitterPostClient {
}

async postTweet(
runtime: IAgentRuntime,
runtime: AgentcoinRuntime,
client: ClientBase,
tweetTextForPosting: string,
roomId: UUID,
Expand Down Expand Up @@ -438,13 +439,15 @@ export class TwitterPostClient {
elizaLogger.log('Generating new tweet')

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'
)
const roomId = stringToUuid('user_twitter_feed:' + this.client.profile.username)

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
Expand All @@ -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
Expand All @@ -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
Expand Down