-
Notifications
You must be signed in to change notification settings - Fork 3.8k
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
- Loading branch information
0xNerd
committed
Dec 16, 2024
1 parent
67f85fb
commit e72eac0
Showing
14 changed files
with
400 additions
and
0 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,6 @@ | ||
* | ||
|
||
!dist/** | ||
!package.json | ||
!readme.md | ||
!tsup.config.ts |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,3 @@ | ||
import eslintGlobalConfig from "../../eslint.config.mjs"; | ||
|
||
export default [...eslintGlobalConfig]; |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,22 @@ | ||
{ | ||
"name": "@ai16z/client-wordpress", | ||
"version": "0.1.5-alpha.5", | ||
"main": "dist/index.js", | ||
"type": "module", | ||
"types": "dist/index.d.ts", | ||
"dependencies": { | ||
"@ai16z/eliza": "workspace:*", | ||
"axios": "1.7.8" | ||
}, | ||
"devDependencies": { | ||
"tsup": "8.3.5" | ||
}, | ||
"scripts": { | ||
"build": "tsup --format esm --dts", | ||
"dev": "tsup --format esm --dts --watch", | ||
"lint": "eslint . --fix" | ||
}, | ||
"peerDependencies": { | ||
"whatwg-url": "7.1.0" | ||
} | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,142 @@ | ||
import { WordpressClient } from "./client"; | ||
import { | ||
elizaLogger, | ||
stringToUuid, | ||
composeContext, | ||
IAgentRuntime, | ||
generateText, | ||
ModelClass | ||
} from "@ai16z/eliza"; | ||
|
||
const wordpressPostTemplate = `{{timeline}} | ||
# Knowledge | ||
{{knowledge}} | ||
About {{agentName}}: | ||
{{bio}} | ||
{{summary}} | ||
{{postDirections}} | ||
{{providers}} | ||
# Task: Generate a blog post in the voice and style of {{agentName}} | ||
Write a post that is {{adjective}} about {{topic}}, from the perspective of {{agentName}}. | ||
`; | ||
|
||
export class WordpressBlogClient { | ||
|
||
runtime: IAgentRuntime; | ||
client: WordpressClient; | ||
|
||
async start() { | ||
await this.client.init(); | ||
|
||
const generateNewPostLoop = async () => { | ||
await this.generateNewBlogPost(); | ||
setTimeout(generateNewPostLoop, 1000 * 60 * 60 * 24); // 24 hours | ||
}; | ||
|
||
generateNewPostLoop(); | ||
|
||
}; | ||
|
||
constructor(client: WordpressClient, runtime: IAgentRuntime) { | ||
this.runtime = runtime; | ||
this.client = client; | ||
} | ||
|
||
private async generateNewBlogPost() { | ||
elizaLogger.log("Generating new blog post"); | ||
|
||
try { | ||
// get the last 5 posts | ||
const posts = await this.client.getPosts(); | ||
const last5Posts = posts.slice(-5); | ||
const formattedPosts = last5Posts.map(post => | ||
`Title: ${post.title.rendered}\nContent: ${post.content.rendered}`).join("\n\n"); | ||
const topics = this.runtime.character.topics.join(', '); | ||
|
||
const state = await this.runtime.composeState( | ||
{ | ||
userId: this.runtime.agentId, | ||
roomId: stringToUuid('wordpress_generate_room'), | ||
agentId: this.runtime.agentId, | ||
content: { | ||
text: topics, | ||
action: '' | ||
} | ||
}, | ||
{ | ||
timeline: formattedPosts, | ||
} | ||
); | ||
const context = composeContext({ | ||
state, | ||
template: this.runtime.character.templates?.wordpressPostTemplate || wordpressPostTemplate | ||
}); | ||
|
||
elizaLogger.debug('Generate post prompt:\n' + context); | ||
|
||
const newBlogContent = await generateText({ | ||
runtime: this.runtime, | ||
context, | ||
modelClass: ModelClass.SMALL | ||
}); | ||
|
||
// Generate a title for the post | ||
const title = await generateText({ | ||
runtime: this.runtime, | ||
context: `Generate a title for the post, only return the title, no other text: ${newBlogContent}`, | ||
modelClass: ModelClass.SMALL | ||
}); | ||
|
||
if (this.runtime.getSetting('WORDPRESS_DRY_RUN') === 'true') { | ||
elizaLogger.info(`Dry run: would have posted:\nTitle: ${title}\nContent: ${newBlogContent}`); | ||
return; | ||
} | ||
try { | ||
elizaLogger.log(`Posting new WordPress blog post:\n${newBlogContent}`); | ||
|
||
const result = await this.client.addToRequestQueue( | ||
async () => await this.client.createPost({ | ||
title: title, | ||
content: newBlogContent, | ||
status: 'draft' | ||
}) | ||
); | ||
|
||
await this.runtime.cacheManager.set( | ||
`wordpress/${this.client.getPublicConfig().username}/lastPost`, | ||
{ | ||
id: result.id, | ||
timestamp: Date.now() | ||
} | ||
); | ||
|
||
const roomId = stringToUuid(`wordpress-post-${result.id}`); | ||
await this.runtime.messageManager.createMemory({ | ||
id: stringToUuid(`${result.id}-${this.runtime.agentId}`), | ||
userId: this.runtime.agentId, | ||
content: { | ||
text: newBlogContent, | ||
url: result.url, | ||
source: 'wordpress' | ||
}, | ||
agentId: this.runtime.agentId, | ||
roomId, | ||
createdAt: Date.now() | ||
}); | ||
|
||
elizaLogger.log(`WordPress post created: ${result.url}`); | ||
} catch (error) { | ||
elizaLogger.error('Error creating WordPress post:', error); | ||
} | ||
|
||
} catch (error) { | ||
elizaLogger.error("Error generating new blog post", error); | ||
} | ||
} | ||
|
||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,98 @@ | ||
import axios, { AxiosInstance } from "axios"; | ||
import { WpConfig, WpPost } from "./types"; | ||
import { IAgentRuntime } from "@ai16z/eliza"; | ||
|
||
class RequestQueue { | ||
private queue: (() => Promise<any>)[] = []; | ||
private processing = false; | ||
|
||
async add<T>(request: () => Promise<T>): Promise<T> { | ||
return new Promise((resolve, reject) => { | ||
this.queue.push(async () => { | ||
try { | ||
const result = await request(); | ||
resolve(result); | ||
} catch (error) { | ||
reject(error); | ||
} | ||
}); | ||
this.processQueue(); | ||
}); | ||
} | ||
|
||
private async processQueue() { | ||
if (this.processing || this.queue.length === 0) { | ||
return; | ||
} | ||
|
||
this.processing = true; | ||
while (this.queue.length > 0) { | ||
const request = this.queue.shift(); | ||
if (!request) continue; | ||
try { | ||
await request(); | ||
} catch (error) { | ||
console.error("Error processing request:", error); | ||
this.queue.unshift(request); | ||
await this.exponentialBackoff(this.queue.length); | ||
} | ||
await this.randomDelay(); | ||
} | ||
this.processing = false; | ||
} | ||
|
||
private async exponentialBackoff(retryCount: number) { | ||
const delay = Math.pow(2, retryCount) * 1000; | ||
await new Promise((resolve) => setTimeout(resolve, delay)); | ||
} | ||
|
||
private async randomDelay() { | ||
const delay = Math.floor(Math.random() * 2000) + 1500; | ||
await new Promise((resolve) => setTimeout(resolve, delay)); | ||
} | ||
} | ||
|
||
export class WordpressClient { | ||
private client: AxiosInstance; | ||
private config: WpConfig; | ||
protected requestQueue: RequestQueue = new RequestQueue(); | ||
|
||
constructor(config: WpConfig) { | ||
this.config = config; | ||
this.client = axios.create({ | ||
baseURL: `${config.url}/wp-json/wp/v2`, | ||
headers: { | ||
Authorization: `Basic ${Buffer.from( | ||
`${config.username}:${config.password}` | ||
).toString("base64")}`, | ||
"Content-Type": "application/json", | ||
}, | ||
}); | ||
} | ||
|
||
async init() { | ||
// check if the client is initialized | ||
if (this.client) { | ||
return; | ||
} | ||
} | ||
|
||
public getPublicConfig() { | ||
const { username } = this.config; | ||
return { username }; | ||
} | ||
|
||
async createPost(post: WpPost): Promise<any> { | ||
const response = await this.client.post("/posts", post); | ||
return response.data; | ||
} | ||
|
||
async getPosts(): Promise<any> { | ||
const response = await this.client.get("/posts"); | ||
return response.data; | ||
} | ||
|
||
public async addToRequestQueue(task: () => Promise<any>) { | ||
return this.requestQueue.add(task); | ||
} | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,40 @@ | ||
import { IAgentRuntime } from "@ai16z/eliza"; | ||
import { z } from "zod"; | ||
|
||
export const wordpressEnvSchema = z.object({ | ||
WORDPRESS_URL: z.string().min(1, "Wordpress url is required"), | ||
WORDPRESS_USERNAME: z.string().min(1, "Wordpress username is required"), | ||
WORDPRESS_PASSWORD: z.string().min(1, "Wordpress password is required"), | ||
}); | ||
|
||
export type WordpressConfig = z.infer<typeof wordpressEnvSchema>; | ||
|
||
export async function validateWordpressConfig( | ||
runtime: IAgentRuntime | ||
): Promise<WordpressConfig> { | ||
try { | ||
const config = { | ||
WORDPRESS_URL: | ||
runtime.getSetting("WORDPRESS_URL") || | ||
process.env.WORDPRESS_URL, | ||
WORDPRESS_USERNAME: | ||
runtime.getSetting("WORDPRESS_USERNAME") || | ||
process.env.WORDPRESS_USERNAME, | ||
WORDPRESS_PASSWORD: | ||
runtime.getSetting("WORDPRESS_PASSWORD") || | ||
process.env.WORDPRESS_PASSWORD, | ||
}; | ||
|
||
return wordpressEnvSchema.parse(config); | ||
} catch (error) { | ||
if (error instanceof z.ZodError) { | ||
const errorMessages = error.errors | ||
.map((err) => `${err.path.join(".")}: ${err.message}`) | ||
.join("\n"); | ||
throw new Error( | ||
`Wordpress configuration validation failed:\n${errorMessages}` | ||
); | ||
} | ||
throw error; | ||
} | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,33 @@ | ||
import { Client, IAgentRuntime, elizaLogger } from "@ai16z/eliza"; | ||
import { WordpressClient } from "./client"; | ||
import { validateWordpressConfig } from "./environment"; | ||
import { WordpressBlogClient } from "./blog"; | ||
|
||
export class WordpressManager { | ||
client: WordpressClient; | ||
blog: WordpressBlogClient; | ||
constructor(runtime: IAgentRuntime) { | ||
this.client = new WordpressClient({ | ||
url: runtime.getSetting("WORDPRESS_URL"), | ||
username: runtime.getSetting("WORDPRESS_USERNAME"), | ||
password: runtime.getSetting("WORDPRESS_PASSWORD"), | ||
}); | ||
this.blog = new WordpressBlogClient(this.client, runtime); | ||
} | ||
} | ||
|
||
export const WordpressClientInterface: Client = { | ||
async start(runtime: IAgentRuntime) { | ||
await validateWordpressConfig(runtime); | ||
const wp = new WordpressManager(runtime); | ||
|
||
wp.blog.start(); | ||
|
||
return wp; | ||
}, | ||
async stop(_runtime: IAgentRuntime) { | ||
elizaLogger.warn("Wordpress client does not support stopping yet"); | ||
}, | ||
}; | ||
|
||
export default WordpressClientInterface; |
Oops, something went wrong.