-
Notifications
You must be signed in to change notification settings - Fork 66
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
Merge pull request #55 from caillef/discord
feat: discord v0.1
- Loading branch information
Showing
7 changed files
with
506 additions
and
17 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
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,172 @@ | ||
/** | ||
* Example demonstrating a Discord bot using the Daydreams package. | ||
* This bot can: | ||
* - Reply to DMs | ||
*/ | ||
|
||
import { Orchestrator } from "../packages/core/src/core/orchestrator"; | ||
import { HandlerRole } from "../packages/core/src/core/types"; | ||
import { DiscordClient } from "../packages/core/src/core/io/discord"; | ||
import { RoomManager } from "../packages/core/src/core/room-manager"; | ||
import { ChromaVectorDB } from "../packages/core/src/core/vector-db"; | ||
import { MessageProcessor } from "../packages/core/src/core/processors/message-processor"; | ||
import { LLMClient } from "../packages/core/src/core/llm-client"; | ||
import { env } from "../packages/core/src/core/env"; | ||
import { LogLevel } from "../packages/core/src/core/types"; | ||
import chalk from "chalk"; | ||
import { defaultCharacter } from "../packages/core/src/core/character"; | ||
import { z } from "zod"; | ||
import readline from "readline"; | ||
import { MongoDb } from "../packages/core/src/core/mongo-db"; | ||
|
||
async function main() { | ||
const loglevel = LogLevel.DEBUG; | ||
|
||
// Initialize core dependencies | ||
const vectorDb = new ChromaVectorDB("discord_agent", { | ||
chromaUrl: "http://localhost:8000", | ||
logLevel: loglevel, | ||
}); | ||
|
||
await vectorDb.purge(); // Clear previous session data | ||
|
||
const roomManager = new RoomManager(vectorDb); | ||
|
||
const llmClient = new LLMClient({ | ||
model: "anthropic/claude-3-5-sonnet-latest", // Using a known supported model | ||
temperature: 0.3, | ||
}); | ||
|
||
// Initialize processor with default character personality | ||
const processor = new MessageProcessor( | ||
llmClient, | ||
defaultCharacter, | ||
loglevel | ||
); | ||
|
||
// Initialize core system | ||
const scheduledTaskDb = new MongoDb( | ||
"mongodb://localhost:27017", | ||
"myApp", | ||
"scheduled_tasks" | ||
); | ||
|
||
await scheduledTaskDb.connect(); | ||
console.log(chalk.green("✅ Scheduled task database connected")); | ||
|
||
await scheduledTaskDb.deleteAll(); | ||
|
||
// Initialize core system | ||
const core = new Orchestrator( | ||
roomManager, | ||
vectorDb, | ||
[processor], | ||
scheduledTaskDb, | ||
{ | ||
level: loglevel, | ||
enableColors: true, | ||
enableTimestamp: true, | ||
} | ||
); | ||
|
||
function messageCreate(bot: any, message: any) { | ||
const isMention = | ||
message.mentions.users.findKey( | ||
(user: any) => user.id === bot.id | ||
) !== undefined; | ||
if (isMention) { | ||
core.dispatchToInput( | ||
"discord_mention", | ||
{ | ||
content: message.content, | ||
sentBy: message.author.id, | ||
channelId: message.channelId, | ||
}, | ||
message.author.id | ||
); | ||
} | ||
} | ||
|
||
// Set up Discord client with credentials | ||
const discord = new DiscordClient( | ||
{ | ||
discord_token: env.DISCORD_TOKEN, | ||
}, | ||
loglevel, | ||
{ | ||
messageCreate, | ||
} | ||
); | ||
|
||
// Register input handler for Discord mentions | ||
core.registerIOHandler({ | ||
name: "discord_mention", | ||
role: HandlerRole.INPUT, | ||
handler: async (data: unknown) => { | ||
const message = data as { content: string; sentBy: string }; | ||
console.log(chalk.blue("🔍 Received Discord mention...")); | ||
|
||
return [message]; | ||
}, | ||
schema: z.object({ | ||
sentBy: z.string(), | ||
content: z.string(), | ||
channelId: z.string(), | ||
}), | ||
}); | ||
|
||
// Register output handler for Discord replies | ||
core.registerIOHandler({ | ||
name: "discord_reply", | ||
role: HandlerRole.OUTPUT, | ||
handler: async (data: unknown) => { | ||
const messageData = data as { | ||
content: string; | ||
channelId: string; | ||
}; | ||
return discord.createMessageOutput().handler(messageData); | ||
}, | ||
schema: z | ||
.object({ | ||
content: z.string(), | ||
channelId: z | ||
.string() | ||
.optional() | ||
.describe("The channel ID of the message"), | ||
}) | ||
.describe( | ||
"If you have been tagged or mentioned in Discord, use this. This is for replying to a message." | ||
), | ||
}); | ||
|
||
// Set up readline interface | ||
const rl = readline.createInterface({ | ||
input: process.stdin, | ||
output: process.stdout, | ||
}); | ||
|
||
// Start the prompt loop | ||
console.log(chalk.cyan("🤖 Bot is now running and monitoring Discord...")); | ||
console.log(chalk.cyan("You can type messages in the console.")); | ||
console.log(chalk.cyan('Type "exit" to quit')); | ||
|
||
// Handle graceful shutdown | ||
process.on("SIGINT", async () => { | ||
console.log(chalk.yellow("\n\nShutting down...")); | ||
|
||
// Clean up resources | ||
discord.destroy(); | ||
core.removeIOHandler("discord_mention"); | ||
core.removeIOHandler("discord_reply"); | ||
rl.close(); | ||
|
||
console.log(chalk.green("✅ Shutdown complete")); | ||
process.exit(0); | ||
}); | ||
} | ||
|
||
// Run the example | ||
main().catch((error) => { | ||
console.error(chalk.red("Fatal error:"), error); | ||
process.exit(1); | ||
}); |
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,160 @@ | ||
import { Client, Events, GatewayIntentBits, Partials, User } from "discord.js"; | ||
import type { JSONSchemaType } from "ajv"; | ||
import { Logger } from "../../core/logger"; | ||
import { LogLevel } from "../types"; | ||
import { env } from "../../core/env"; | ||
|
||
export interface DiscordCredentials { | ||
discord_token: string; | ||
} | ||
|
||
export interface MessageData { | ||
content: string; | ||
channelId: string; | ||
conversationId?: string; | ||
sendBy?: string; | ||
} | ||
|
||
export interface EventCallbacks { | ||
messageCreate?: (bot: any, message: any) => void; | ||
} | ||
|
||
// Schema for message output validation | ||
export const messageSchema: JSONSchemaType<MessageData> = { | ||
type: "object", | ||
properties: { | ||
content: { type: "string" }, | ||
channelId: { type: "string" }, | ||
sendBy: { type: "string", nullable: true }, | ||
conversationId: { type: "string", nullable: true }, | ||
}, | ||
required: ["content", "channelId"], | ||
additionalProperties: false, | ||
}; | ||
|
||
export class DiscordClient { | ||
private client: Client; | ||
private logger: Logger; | ||
|
||
constructor( | ||
private credentials: DiscordCredentials, | ||
logLevel: LogLevel = LogLevel.INFO, | ||
eventCallbacks: EventCallbacks | ||
) { | ||
this.client = new Client({ | ||
intents: [ | ||
GatewayIntentBits.Guilds, | ||
GatewayIntentBits.GuildMessages, | ||
GatewayIntentBits.MessageContent, | ||
GatewayIntentBits.GuildMembers, | ||
GatewayIntentBits.DirectMessages, | ||
GatewayIntentBits.DirectMessageTyping, | ||
GatewayIntentBits.DirectMessageReactions, | ||
], | ||
partials: [Partials.Channel], // Enable DM | ||
}); | ||
this.credentials = credentials; | ||
this.logger = new Logger({ | ||
level: logLevel, | ||
enableColors: true, | ||
enableTimestamp: true, | ||
}); | ||
|
||
if (eventCallbacks.messageCreate) { | ||
this.client.on(Events.MessageCreate, (message) => { | ||
if (eventCallbacks.messageCreate) { | ||
eventCallbacks.messageCreate(this.client.user, message); | ||
} | ||
}); | ||
} | ||
|
||
this.client.on(Events.ClientReady, async () => { | ||
this.logger.info("DiscordClient", "Initialized successfully"); | ||
}); | ||
|
||
this.client.login(this.credentials.discord_token).catch((error) => { | ||
this.logger.error("DiscordClient", "Failed to login", { error }); | ||
console.error("Login error:", error); | ||
}); | ||
} | ||
|
||
public destroy() { | ||
this.client.destroy(); | ||
} | ||
|
||
/** | ||
* Create an output for sending messages | ||
*/ | ||
public createMessageOutput() { | ||
return { | ||
name: "discord_message", | ||
handler: async (data: MessageData) => { | ||
return await this.sendMessage(data); | ||
}, | ||
response: { | ||
success: "boolean", | ||
channelId: "string", | ||
}, | ||
schema: messageSchema, | ||
}; | ||
} | ||
|
||
private async sendMessage(data: MessageData) { | ||
try { | ||
this.logger.info( | ||
"DiscordClient.sendMessage", | ||
"Would send message", | ||
{ | ||
data, | ||
} | ||
); | ||
|
||
if (env.DRY_RUN) { | ||
return { | ||
success: true, | ||
channelId: "DRY RUN CHANNEL ID", | ||
}; | ||
} | ||
|
||
const channel = this.client.channels.cache.get(data.channelId); | ||
if (!channel?.isTextBased()) { | ||
const error = new Error( | ||
`Invalid or unsupported channel: ${data.channelId}` | ||
); | ||
this.logger.error( | ||
"DiscordClient.sendMessage", | ||
"Error sending message", | ||
{ | ||
error, | ||
} | ||
); | ||
throw error; | ||
} | ||
const sentMessage = await channel.send(data.content); | ||
|
||
return { | ||
success: true, | ||
messageId: sentMessage.id, | ||
}; | ||
} catch (error) { | ||
this.logger.error( | ||
"DiscordClient.sendMessage", | ||
"Error sending message", | ||
{ | ||
error, | ||
} | ||
); | ||
throw error; | ||
} | ||
} | ||
} | ||
|
||
// Example usage: | ||
/* | ||
const discord = new DiscordClient({ | ||
discord_token: "M..." | ||
}); | ||
// Register output | ||
core.registerOutput(discord.createMessageOutput()); | ||
*/ |
Oops, something went wrong.