@@ -5,67 +5,48 @@ import {
5
5
State ,
6
6
composeContext ,
7
7
elizaLogger ,
8
- generateText ,
9
8
ModelClass ,
10
9
formatMessages ,
10
+ generateObject ,
11
11
} from "@elizaos/core" ;
12
12
import { Scraper } from "agent-twitter-client" ;
13
+ import { tweetTemplate } from "../templates" ;
14
+ import { isTweetContent , TweetSchema } from "../types" ;
13
15
14
16
async function composeTweet (
15
17
runtime : IAgentRuntime ,
16
- message : Memory ,
18
+ _message : Memory ,
17
19
state ?: State
18
20
) : Promise < string > {
19
21
try {
20
- // Get recent conversation history
21
- const recentMessages = await runtime . messageManager . getMemories ( {
22
- roomId : message . roomId ,
23
- count : 5 ,
24
- } ) ;
25
-
26
- const formattedHistory = formatMessages ( {
27
- messages : recentMessages ,
28
- actors : state ?. actorsData ,
29
- } ) ;
30
-
31
- // Template for generating the tweet
32
- const tweetTemplate = `
33
- # Context
34
- Recent conversation:
35
- ${ formattedHistory }
36
-
37
- Character style:
38
- ${ runtime . character . style . post . join ( "\n" ) }
39
-
40
- Topics of expertise:
41
- ${ runtime . character . topics . join ( ", " ) }
42
-
43
- # Task
44
- Generate a tweet that:
45
- 1. Relates to the recent conversation or requested topic
46
- 2. Matches the character's style and voice
47
- 3. Is concise and engaging
48
- 4. Must be UNDER 180 characters (this is a strict requirement)
49
- 5. Speaks from the perspective of ${ runtime . character . name }
50
-
51
- Generate only the tweet text, no other commentary.` ;
52
-
53
- const context = await composeContext ( {
22
+ const context = composeContext ( {
54
23
state,
55
24
template : tweetTemplate ,
56
25
} ) ;
57
26
58
- const tweetContent = await generateText ( {
27
+ const tweetContentObject = await generateObject ( {
59
28
runtime,
60
29
context,
61
30
modelClass : ModelClass . SMALL ,
31
+ schema : TweetSchema ,
62
32
stop : [ "\n" ] ,
63
33
} ) ;
64
34
65
- const trimmedContent = tweetContent . trim ( ) ;
35
+ if ( ! isTweetContent ( tweetContentObject . object ) ) {
36
+ elizaLogger . error (
37
+ "Invalid tweet content:" ,
38
+ tweetContentObject . object
39
+ ) ;
40
+ return ;
41
+ }
42
+
43
+ const trimmedContent = tweetContentObject . object . text . trim ( ) ;
66
44
67
- // Enforce character limit
68
- if ( trimmedContent . length > 180 ) {
45
+ // Skip truncation if TWITTER_PREMIUM is true
46
+ if (
47
+ process . env . TWITTER_PREMIUM ?. toLowerCase ( ) !== "true" &&
48
+ trimmedContent . length > 180
49
+ ) {
69
50
elizaLogger . warn (
70
51
`Tweet too long (${ trimmedContent . length } chars), truncating...`
71
52
) ;
@@ -88,15 +69,17 @@ async function postTweet(content: string): Promise<boolean> {
88
69
const twitter2faSecret = process . env . TWITTER_2FA_SECRET ;
89
70
90
71
if ( ! username || ! password ) {
91
- throw new Error (
72
+ elizaLogger . error (
92
73
"Twitter credentials not configured in environment"
93
74
) ;
75
+ return false ;
94
76
}
95
77
96
78
// Login with credentials
97
79
await scraper . login ( username , password , email , twitter2faSecret ) ;
98
80
if ( ! ( await scraper . isLoggedIn ( ) ) ) {
99
- throw new Error ( "Failed to login to Twitter" ) ;
81
+ elizaLogger . error ( "Failed to login to Twitter" ) ;
82
+ return false ;
100
83
}
101
84
102
85
// Send the tweet
@@ -109,16 +92,18 @@ async function postTweet(content: string): Promise<boolean> {
109
92
// Check for Twitter API errors
110
93
if ( body . errors ) {
111
94
const error = body . errors [ 0 ] ;
112
- throw new Error (
95
+ elizaLogger . error (
113
96
`Twitter API error (${ error . code } ): ${ error . message } `
114
97
) ;
98
+ return false ;
115
99
}
116
100
117
101
// Check for successful tweet creation
118
102
if ( ! body ?. data ?. create_tweet ?. tweet_results ?. result ) {
119
- throw new Error (
103
+ elizaLogger . error (
120
104
"Failed to post tweet: No tweet result in response"
121
105
) ;
106
+ return false ;
122
107
}
123
108
124
109
return true ;
@@ -186,12 +171,64 @@ export const postAction: Action = {
186
171
[
187
172
{
188
173
user : "{{user1}}" ,
189
- content : { text : "Share your thoughts on AI" } ,
174
+ content : { text : "You should tweet that" } ,
175
+ } ,
176
+ {
177
+ user : "{{agentName}}" ,
178
+ content : {
179
+ text : "I'll share this update with my followers right away!" ,
180
+ action : "POST_TWEET" ,
181
+ } ,
182
+ } ,
183
+ ] ,
184
+ [
185
+ {
186
+ user : "{{user1}}" ,
187
+ content : { text : "Post this tweet" } ,
188
+ } ,
189
+ {
190
+ user : "{{agentName}}" ,
191
+ content : {
192
+ text : "I'll post that as a tweet now." ,
193
+ action : "POST_TWEET" ,
194
+ } ,
195
+ } ,
196
+ ] ,
197
+ [
198
+ {
199
+ user : "{{user1}}" ,
200
+ content : { text : "Share that on Twitter" } ,
201
+ } ,
202
+ {
203
+ user : "{{agentName}}" ,
204
+ content : {
205
+ text : "I'll share this message on Twitter." ,
206
+ action : "POST_TWEET" ,
207
+ } ,
208
+ } ,
209
+ ] ,
210
+ [
211
+ {
212
+ user : "{{user1}}" ,
213
+ content : { text : "Post that on X" } ,
214
+ } ,
215
+ {
216
+ user : "{{agentName}}" ,
217
+ content : {
218
+ text : "I'll post this message on X right away." ,
219
+ action : "POST_TWEET" ,
220
+ } ,
221
+ } ,
222
+ ] ,
223
+ [
224
+ {
225
+ user : "{{user1}}" ,
226
+ content : { text : "You should put that on X dot com" } ,
190
227
} ,
191
228
{
192
229
user : "{{agentName}}" ,
193
230
content : {
194
- text : "The future of AI lies in responsible development and ethical considerations. We must ensure it benefits all of humanity ." ,
231
+ text : "I'll put this message up on X.com now ." ,
195
232
action : "POST_TWEET" ,
196
233
} ,
197
234
} ,
0 commit comments