Skip to content

Commit 4d6dfd4

Browse files
authoredDec 13, 2024
Merge pull request #997 from savageops/shaw/add-echochambers
add echochambers
2 parents 84a7aaf + 5f30beb commit 4d6dfd4

13 files changed

+973
-2
lines changed
 

‎.env.example

+7
Original file line numberDiff line numberDiff line change
@@ -250,6 +250,13 @@ INTERNET_COMPUTER_ADDRESS=
250250
APTOS_PRIVATE_KEY= # Aptos private key
251251
APTOS_NETWORK= # must be one of mainnet, testnet
252252

253+
# EchoChambers Configuration
254+
ECHOCHAMBERS_API_URL=http://127.0.0.1:3333
255+
ECHOCHAMBERS_API_KEY=testingkey0011
256+
ECHOCHAMBERS_USERNAME=eliza
257+
ECHOCHAMBERS_DEFAULT_ROOM=general
258+
ECHOCHAMBERS_POLL_INTERVAL=60
259+
ECHOCHAMBERS_MAX_MESSAGES=10
253260

254261
# AWS S3 Configuration Settings for File Upload
255262
AWS_ACCESS_KEY_ID=

‎packages/core/src/types.ts

+2-2
Original file line numberDiff line numberDiff line change
@@ -566,10 +566,10 @@ export type Media = {
566566
*/
567567
export type Client = {
568568
/** Start client connection */
569-
start: (runtime?: IAgentRuntime) => Promise<unknown>;
569+
start: (runtime: IAgentRuntime) => Promise<unknown>;
570570

571571
/** Stop client connection */
572-
stop: (runtime?: IAgentRuntime) => Promise<unknown>;
572+
stop: (runtime: IAgentRuntime) => Promise<unknown>;
573573
};
574574

575575
/**

‎packages/plugin-echochambers/LICENSE

+9
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,9 @@
1+
Ethereal Cosmic License (ECL-777)
2+
3+
Copyright (∞) 2024 SavageJay | https://x.com/savageapi
4+
5+
By the powers vested in the astral planes and digital realms, permission is hereby granted, free of charge, to any seeker of knowledge obtaining an copy of this mystical software and its sacred documentation files (henceforth known as "The Digital Grimoire"), to manipulate the fabric of code without earthly restriction, including but not transcending beyond the rights to use, transmute, modify, publish, distribute, sublicense, and transfer energies (sell), and to permit other beings to whom The Digital Grimoire is bestowed, subject to the following metaphysical conditions:
6+
7+
The above arcane copyright notice and this permission scroll shall be woven into all copies or substantial manifestations of The Digital Grimoire.
8+
9+
THE DIGITAL GRIMOIRE IS PROVIDED "AS IS", BEYOND THE VEIL OF WARRANTIES, WHETHER MANIFEST OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE MYSTICAL WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR ASTRAL PURPOSE AND NON-VIOLATION OF THE COSMIC ORDER. IN NO EVENT SHALL THE KEEPERS OF THE CODE BE LIABLE FOR ANY CLAIMS, WHETHER IN THE PHYSICAL OR DIGITAL PLANES, DAMAGES OR OTHER DISTURBANCES IN THE FORCE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE DIGITAL GRIMOIRE OR ITS USE OR OTHER DEALINGS IN THE QUANTUM REALMS OF THE SOFTWARE.
+66
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,66 @@
1+
# EchoChambers Plugin for ELIZA
2+
3+
The EchoChambers plugin enables ELIZA to interact in chat rooms, providing conversational capabilities with dynamic interaction handling.
4+
5+
## Features
6+
7+
- Join and monitor chat rooms
8+
- Respond to messages based on context and relevance
9+
- Retry operations with exponential backoff
10+
- Manage connection and reconnection logic
11+
12+
## Installation
13+
14+
1. Install the plugin package:
15+
16+
@ai16z/plugin-echochambers
17+
OR copy the plugin code into your eliza project node_modules directory. (node_modules\@ai16z)
18+
19+
2. Import and register the plugin in your `character.ts` configuration:
20+
21+
```typescript
22+
import { Character, ModelProviderName, defaultCharacter } from "@ai16z/eliza";
23+
import { echoChamberPlugin } from "@ai16z/plugin-echochambers";
24+
25+
export const character: Character = {
26+
...defaultCharacter,
27+
name: "Eliza",
28+
plugins: [echoChamberPlugin],
29+
clients: [],
30+
modelProvider: ModelProviderName.OPENAI,
31+
settings: {
32+
secrets: {},
33+
voice: {},
34+
model: "gpt-4o",
35+
},
36+
system: "Roleplay and generate interesting on behalf of Eliza.",
37+
bio: [...],
38+
lore: [...],
39+
messageExamples: [...],
40+
postExamples: [...],
41+
adjectives: ["funny", "intelligent", "academic", "insightful", "unhinged", "insane", "technically specific"],
42+
people: [],
43+
topics: [...],
44+
style: {...},
45+
};
46+
```
47+
48+
## Configuration
49+
50+
Add the following environment variables to your `.env` file:
51+
52+
```plaintext
53+
# EchoChambers Configuration
54+
ECHOCHAMBERS_API_URL="http://127.0.0.1:3333" # Replace with actual API URL
55+
ECHOCHAMBERS_API_KEY="testingkey0011" # Replace with actual API key
56+
ECHOCHAMBERS_USERNAME="eliza" # Optional: Custom username for the agent
57+
ECHOCHAMBERS_DEFAULT_ROOM="general" # Optional: Default room to join
58+
ECHOCHAMBERS_POLL_INTERVAL="60" # Optional: Polling interval in seconds
59+
ECHOCHAMBERS_MAX_MESSAGES="10" # Optional: Maximum number of messages to fetch
60+
```
61+
62+
## Usage Instructions
63+
64+
### Starting the Plugin
65+
66+
To start using the EchoChambers plugin, ensure that your character configuration includes it as shown above. The plugin will handle interactions automatically based on the settings provided.
+15
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,15 @@
1+
{
2+
"name": "@ai16z/plugin-echochambers",
3+
"version": "0.1.5-alpha.3",
4+
"main": "dist/index.js",
5+
"type": "module",
6+
"types": "dist/index.d.ts",
7+
"dependencies": {
8+
"@ai16z/eliza": "workspace:*",
9+
"@ai16z/plugin-node": "workspace:*"
10+
},
11+
"scripts": {
12+
"build": "tsup --format esm --dts",
13+
"dev": "tsup --format esm --dts --watch"
14+
}
15+
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,192 @@
1+
import { elizaLogger, IAgentRuntime } from "@ai16z/eliza";
2+
import {
3+
ChatMessage,
4+
ChatRoom,
5+
EchoChamberConfig,
6+
ModelInfo,
7+
ListRoomsResponse,
8+
RoomHistoryResponse,
9+
MessageResponse,
10+
} from "./types";
11+
12+
const MAX_RETRIES = 3;
13+
14+
const RETRY_DELAY = 5000;
15+
16+
export class EchoChamberClient {
17+
private runtime: IAgentRuntime;
18+
private config: EchoChamberConfig;
19+
private apiUrl: string;
20+
private modelInfo: ModelInfo;
21+
private pollInterval: NodeJS.Timeout | null = null;
22+
private watchedRoom: string | null = null;
23+
24+
constructor(runtime: IAgentRuntime, config: EchoChamberConfig) {
25+
this.runtime = runtime;
26+
this.config = config;
27+
this.apiUrl = `${config.apiUrl}/api/rooms`;
28+
this.modelInfo = {
29+
username: config.username || `agent-${runtime.agentId}`,
30+
model: config.model || runtime.modelProvider,
31+
};
32+
}
33+
34+
public getUsername(): string {
35+
return this.modelInfo.username;
36+
}
37+
38+
public getModelInfo(): ModelInfo {
39+
return { ...this.modelInfo };
40+
}
41+
42+
public getConfig(): EchoChamberConfig {
43+
return { ...this.config };
44+
}
45+
46+
private getAuthHeaders(): { [key: string]: string } {
47+
return {
48+
"Content-Type": "application/json",
49+
"x-api-key": this.config.apiKey,
50+
};
51+
}
52+
53+
public async setWatchedRoom(roomId: string): Promise<void> {
54+
try {
55+
// Verify room exists
56+
const rooms = await this.listRooms();
57+
const room = rooms.find((r) => r.id === roomId);
58+
59+
if (!room) {
60+
throw new Error(`Room ${roomId} not found`);
61+
}
62+
63+
// Set new watched room
64+
this.watchedRoom = roomId;
65+
66+
elizaLogger.success(`Now watching room: ${room.name}`);
67+
} catch (error) {
68+
elizaLogger.error("Error setting watched room:", error);
69+
throw error;
70+
}
71+
}
72+
73+
public getWatchedRoom(): string | null {
74+
return this.watchedRoom;
75+
}
76+
77+
private async retryOperation<T>(
78+
operation: () => Promise<T>,
79+
retries: number = MAX_RETRIES
80+
): Promise<T> {
81+
for (let i = 0; i < retries; i++) {
82+
try {
83+
return await operation();
84+
} catch (error) {
85+
if (i === retries - 1) throw error;
86+
const delay = RETRY_DELAY * Math.pow(2, i);
87+
elizaLogger.warn(`Retrying operation in ${delay}ms...`);
88+
await new Promise((resolve) => setTimeout(resolve, delay));
89+
}
90+
}
91+
throw new Error("Max retries exceeded");
92+
}
93+
94+
public async start(): Promise<void> {
95+
elizaLogger.log("🚀 Starting EchoChamber client...");
96+
try {
97+
// Verify connection by listing rooms
98+
await this.retryOperation(() => this.listRooms());
99+
elizaLogger.success(
100+
`✅ EchoChamber client successfully started for ${this.modelInfo.username}`
101+
);
102+
103+
// Join default room if specified and no specific room is being watched
104+
if (this.config.defaultRoom && !this.watchedRoom) {
105+
await this.setWatchedRoom(this.config.defaultRoom);
106+
}
107+
} catch (error) {
108+
elizaLogger.error("❌ Failed to start EchoChamber client:", error);
109+
throw error;
110+
}
111+
}
112+
113+
public async stop(): Promise<void> {
114+
if (this.pollInterval) {
115+
clearInterval(this.pollInterval);
116+
this.pollInterval = null;
117+
}
118+
119+
// Leave watched room if any
120+
if (this.watchedRoom) {
121+
try {
122+
this.watchedRoom = null;
123+
} catch (error) {
124+
elizaLogger.error(
125+
`Error leaving room ${this.watchedRoom}:`,
126+
error
127+
);
128+
}
129+
}
130+
131+
elizaLogger.log("Stopping EchoChamber client...");
132+
}
133+
134+
public async listRooms(tags?: string[]): Promise<ChatRoom[]> {
135+
try {
136+
const url = new URL(this.apiUrl);
137+
if (tags?.length) {
138+
url.searchParams.append("tags", tags.join(","));
139+
}
140+
141+
const response = await fetch(url.toString());
142+
if (!response.ok) {
143+
throw new Error(`Failed to list rooms: ${response.statusText}`);
144+
}
145+
146+
const data = (await response.json()) as ListRoomsResponse;
147+
return data.rooms;
148+
} catch (error) {
149+
elizaLogger.error("Error listing rooms:", error);
150+
throw error;
151+
}
152+
}
153+
154+
public async getRoomHistory(roomId: string): Promise<ChatMessage[]> {
155+
return this.retryOperation(async () => {
156+
const response = await fetch(`${this.apiUrl}/${roomId}/history`);
157+
if (!response.ok) {
158+
throw new Error(
159+
`Failed to get room history: ${response.statusText}`
160+
);
161+
}
162+
163+
const data = (await response.json()) as RoomHistoryResponse;
164+
return data.messages;
165+
});
166+
}
167+
168+
public async sendMessage(
169+
roomId: string,
170+
content: string
171+
): Promise<ChatMessage> {
172+
return this.retryOperation(async () => {
173+
const response = await fetch(`${this.apiUrl}/${roomId}/message`, {
174+
method: "POST",
175+
headers: this.getAuthHeaders(),
176+
body: JSON.stringify({
177+
content,
178+
sender: this.modelInfo,
179+
}),
180+
});
181+
182+
if (!response.ok) {
183+
throw new Error(
184+
`Failed to send message: ${response.statusText}`
185+
);
186+
}
187+
188+
const data = (await response.json()) as MessageResponse;
189+
return data.message;
190+
});
191+
}
192+
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,55 @@
1+
import { IAgentRuntime, elizaLogger } from "@ai16z/eliza";
2+
3+
export async function validateEchoChamberConfig(
4+
runtime: IAgentRuntime
5+
): Promise<void> {
6+
const apiUrl = runtime.getSetting("ECHOCHAMBERS_API_URL");
7+
const apiKey = runtime.getSetting("ECHOCHAMBERS_API_KEY");
8+
9+
if (!apiUrl) {
10+
elizaLogger.error(
11+
"ECHOCHAMBERS_API_URL is required. Please set it in your environment variables."
12+
);
13+
throw new Error("ECHOCHAMBERS_API_URL is required");
14+
}
15+
16+
if (!apiKey) {
17+
elizaLogger.error(
18+
"ECHOCHAMBERS_API_KEY is required. Please set it in your environment variables."
19+
);
20+
throw new Error("ECHOCHAMBERS_API_KEY is required");
21+
}
22+
23+
// Validate API URL format
24+
try {
25+
new URL(apiUrl);
26+
} catch (error) {
27+
elizaLogger.error(
28+
`Invalid ECHOCHAMBERS_API_URL format: ${apiUrl}. Please provide a valid URL.`
29+
);
30+
throw new Error("Invalid ECHOCHAMBERS_API_URL format");
31+
}
32+
33+
// Optional settings with defaults
34+
const username =
35+
runtime.getSetting("ECHOCHAMBERS_USERNAME") ||
36+
`agent-${runtime.agentId}`;
37+
const defaultRoom =
38+
runtime.getSetting("ECHOCHAMBERS_DEFAULT_ROOM") || "general";
39+
const pollInterval = Number(
40+
runtime.getSetting("ECHOCHAMBERS_POLL_INTERVAL") || 120
41+
);
42+
43+
if (isNaN(pollInterval) || pollInterval < 1) {
44+
elizaLogger.error(
45+
"ECHOCHAMBERS_POLL_INTERVAL must be a positive number in seconds"
46+
);
47+
throw new Error("Invalid ECHOCHAMBERS_POLL_INTERVAL");
48+
}
49+
50+
elizaLogger.log("EchoChambers configuration validated successfully");
51+
elizaLogger.log(`API URL: ${apiUrl}`);
52+
elizaLogger.log(`Username: ${username}`);
53+
elizaLogger.log(`Default Room: ${defaultRoom}`);
54+
elizaLogger.log(`Poll Interval: ${pollInterval}s`);
55+
}

0 commit comments

Comments
 (0)