Discord Plugin
Overview
The Discord Plugin (@tokenring-ai/discord) provides comprehensive Discord integration for TokenRing agents, enabling AI-powered interactions through Discord's messaging system. The package supports a multi-bot architecture with channel-based routing, direct message support, and built-in escalation provider integration.
Key Features
- Multi-bot architecture: Run multiple Discord bots with independent configurations simultaneously
- Channel-based routing: Configure specific agents for different Discord channels
- Direct message support: Enable DM interactions with configurable user authorization
- Per-channel agent isolation: Each channel maintains persistent agent context
- Buffered streaming responses: Intelligent message chunking with 250ms rate limiting
- Message editing: Update existing messages instead of creating new ones
- Attachment handling: Process file attachments with configurable size limits (default 20MB)
- Escalation integration: Built-in
DiscordEscalationProviderfor admin communications - Reply-tracked communication: Track user replies to bot messages for escalation workflows
- Graceful shutdown: Clean agent cleanup and bot disconnection
- Authorization controls: Per-channel and per-user access restrictions
- Join announcements: Optional welcome messages when bots join channels
Integration with TokenRing Ecosystem
This plugin integrates with the TokenRing agent system, allowing Discord users to interact with AI agents through Discord's messaging platform. The plugin creates a separate agent for each Discord channel, maintaining individual conversation contexts. It also integrates with the escalation system for admin communications.
Core Components
DiscordService
The main service that manages multiple Discord bot instances.
Class: DiscordService implements TokenRingService
Properties:
name: string- Service name ("DiscordService")description: string- Service description ("Manages multiple Discord bots for interacting with TokenRing agents.")
Methods:
getAvailableBots(): string[]- Returns array of registered bot namesgetBot(botName: string): DiscordBot | undefined- Returns a specific bot instancerun(signal: AbortSignal): Promise<void>- Starts all configured bots and handles shutdown
Constructor:
constructor(app: TokenRingApp, options: ParsedDiscordServiceConfig)
Internal Implementation:
- Uses
KeyedRegistryto manage multipleDiscordBotinstances - Starts all configured bots on initialization
- Handles graceful shutdown by stopping all bots and cleaning up resources
DiscordBot
Handles individual bot operations including message processing, agent management, and communication.
Class: DiscordBot
Methods:
start(): Promise<void>- Initializes and starts the Discord botstop(): Promise<void>- Stops the bot and cleans up resourcesgetBotUserId(): string | undefined- Returns the Discord user ID of the botcreateCommunicationChannelWithChannel(channelName: string): CommunicationChannel- Creates a communication channel for escalationcreateCommunicationChannelWithUser(userId: string): CommunicationChannel- Creates a DM communication channel
Internal Methods:
handleMessage(message: Message): Promise<void>- Processes incoming Discord messageshandleDirectMessage(message: Message, userId: string, channelId: string, text: string): Promise<void>- Handles DM messagesextractAllAttachments(message: Message): Promise<InputAttachment[]>- Downloads and processes file attachmentsensureAgentForChannel(channelId: string, agentType: string): Promise<Agent>- Ensures an agent exists for a channelflushBuffer(channelId: string): Promise<void>- Sends buffered messages to DiscordsendMessage(channelId: string, text: string): Promise<string>- Sends a message to a channelupdateMessageWithFallback(channelId: string, messageId: string, text: string): Promise<string>- Updates a message with fallback to new messageagentEventLoop(channelId: string, agent: Agent, signal: AbortSignal): Promise<void>- Processes agent events for a channel
Key Features:
- Message buffering: Accumulates agent output and sends in chunks
- Rate limiting: 250ms delay between messages to respect Discord limits
- Message editing: Attempts to update existing messages before creating new ones
- Reply tracking: Tracks user replies to enable escalation workflows
- Attachment processing: Downloads and converts attachments to base64 for agent processing
DiscordEscalationProvider
Integration with the escalation system for admin communications via Discord.
Class: DiscordEscalationProvider implements EscalationProvider
Constructor:
constructor(config: ParsedDiscordEscalationProviderConfig)
Methods:
createCommunicationChannelWithUser(channelName: string, agent: Agent): Promise<CommunicationChannel>- Creates a communication channel for escalation
Implementation:
- Retrieves the configured bot from
DiscordService - Creates a communication channel for the specified channel configuration
- Enables escalation workflows through Discord
splitIntoChunks
Utility function for splitting long messages into Discord-compatible chunks.
Function: splitIntoChunks(text: string | null): string[]
Parameters:
text: string | null- The text to split
Returns: Array of message chunks (max 1990 characters each)
Behavior:
- Splits text at markdown headers (
#) when possible for better formatting - Falls back to character-based splitting at 1990 character limit
- Returns working messages for null input (e.g., "Working...", "Processing...")
Services
DiscordService
The primary service that manages Discord bot operations.
Service Type: TokenRingService
Registration: Automatically registered when the plugin is installed with Discord configuration.
Lifecycle:
- Initialization: Creates
DiscordBotinstances for each configured bot - Startup: Logs in all bots and begins message processing
- Runtime: Handles messages, manages agents, processes events
- Shutdown: Flushes buffers, cleans up agents, disconnects bots
State Management:
- Maintains
KeyedRegistryof bot instances - Each bot manages its own channel agents, chat responses, and user channels
Provider Documentation
DiscordEscalationProvider
The package provides a built-in escalation provider for Discord-based admin communications.
Provider Interface: EscalationProvider
Configuration Schema:
export const DiscordEscalationProviderConfigSchema = z.object({
type: z.literal('discord'),
bot: z.string(),
channel: z.string(),
});
Provider Registration:
- Registered automatically when escalation configuration includes providers with
type: "discord" - Can also be registered programmatically with
EscalationService.registerProvider()
Usage:
- Creates communication channels for escalation workflows
- Routes escalation messages to specified Discord channels
- Supports both guild channels and direct messages
RPC Endpoints
This package does not expose RPC endpoints. Communication is handled through Discord's native messaging system and the escalation service's communication channel interface.
Chat Commands
The Discord plugin does not define traditional slash commands. Instead, it processes messages through Discord's native messaging:
Message Processing
Guild Channel Messages:
- Bot must be mentioned to trigger processing (e.g.,
@BotName what is the weather?) - Mention is stripped from the message content
- Message is sent to the channel's agent
Direct Messages:
- Any message to the bot is processed (if DMs are enabled)
- No mention required
Reply Messages:
- Replies to bot messages are tracked for escalation workflows
- Reply content is sent back to the agent as user input
Authorization
Channel Authorization:
allowedUsers: string[]- Empty array allows all users- Non-empty array restricts to specified user IDs
- Unauthorized users receive "Sorry, you are not authorized."
DM Authorization:
dmAllowedUsers: string[]- Empty array allows all users (ifdmAgentTypeis set)- Non-empty array restricts to specified user IDs
- Unauthorized users receive "Sorry, you are not authorized to DM this bot."
Configuration
Configuration Schemas
DiscordBotConfigSchema
Configuration for a single Discord bot instance.
export const DiscordBotConfigSchema = z.object({
name: z.string(),
botToken: z.string().min(1, "Bot token is required"),
joinMessage: z.string().optional(),
maxFileSize: z.number().default(20_971_520),
channels: z.record(z.string(), z.object({
channelId: z.string(),
allowedUsers: z.array(z.string()).default([]),
agentType: z.string(),
})),
dmAgentType: z.string().optional(),
dmAllowedUsers: z.array(z.string()).default([]),
});
Properties:
| Property | Type | Required | Default | Description |
|---|---|---|---|---|
name | string | Yes | - | Display name for the bot |
botToken | string | Yes | - | Discord bot token |
joinMessage | string | No | - | Message sent when bot joins a channel |
maxFileSize | number | No | 20971520 (20MB) | Maximum attachment size in bytes |
channels | Record | Yes | - | Channel configurations |
dmAgentType | string | No | - | Agent type for DM interactions |
dmAllowedUsers | string[] | No | [] | Authorized user IDs for DMs |
Channel Configuration
Each channel in the channels record:
{
channelId: string,
allowedUsers: string[],
agentType: string
}
Properties:
| Property | Type | Required | Default | Description |
|---|---|---|---|---|
channelId | string | Yes | - | Discord channel ID |
allowedUsers | string[] | No | [] | Authorized user IDs (empty = all users) |
agentType | string | Yes | - | Type of agent to use for this channel |
DiscordServiceConfigSchema
Configuration for the Discord service.
export const DiscordServiceConfigSchema = z.object({
bots: z.record(z.string(), DiscordBotConfigSchema)
});
Properties:
| Property | Type | Required | Description |
|---|---|---|---|
bots | Record<string, DiscordBotConfig> | Yes | Map of bot names to configurations |
DiscordEscalationProviderConfigSchema
Configuration for the Discord escalation provider.
export const DiscordEscalationProviderConfigSchema = z.object({
type: z.literal('discord'),
bot: z.string(),
channel: z.string(),
});
Properties:
| Property | Type | Required | Description |
|---|---|---|---|
type | "discord" | Yes | Provider type identifier |
bot | string | Yes | Name of the bot to use |
channel | string | Yes | Name of the channel configuration |
Full Configuration Example
const config = {
discord: {
bots: {
primary: {
name: "Primary Bot",
botToken: process.env.DISCORD_BOT_TOKEN!,
joinMessage: "🤖 TokenRing bot is online!",
maxFileSize: 20_971_520,
channels: {
engineering: {
channelId: "123456789012345678",
allowedUsers: [],
agentType: "teamLeader"
},
support: {
channelId: "987654321098765432",
allowedUsers: ["111111111111111111", "222222222222222222"],
agentType: "supportAgent"
}
},
dmAgentType: "personalAgent",
dmAllowedUsers: ["111111111111111111"]
}
}
},
escalation: {
providers: {
discordAdmins: {
type: "discord",
bot: "primary",
channel: "engineering"
}
}
}
};
Integration
Plugin Registration
The package provides a TokenRing plugin that automatically registers services:
import discordPlugin from "@tokenring-ai/discord/plugin";
const app = new TokenRingApp();
await app.installPlugin(discordPlugin, {
discord: {
bots: {
primary: {
name: "Primary Bot",
botToken: process.env.DISCORD_BOT_TOKEN!,
channels: {
general: {
channelId: "123456789012345678",
allowedUsers: [],
agentType: "teamLeader"
}
}
}
}
}
});
Service Registration
The plugin automatically registers:
- DiscordService: Manages bot instances
- DiscordEscalationProvider: Registered with EscalationService for providers with
type: "discord"
Programmatic Registration
import TokenRingApp from "@tokenring-ai/app";
import DiscordService from "@tokenring-ai/discord/DiscordService";
import { DiscordEscalationProvider } from "@tokenring-ai/discord";
import { EscalationService } from "@tokenring-ai/escalation";
const app = new TokenRingApp();
// Register Discord service directly
const discordService = new DiscordService(app, {
bots: {
primary: {
name: "Primary Bot",
botToken: process.env.DISCORD_BOT_TOKEN!,
channels: {
general: {
channelId: "123456789012345678",
allowedUsers: [],
agentType: "teamLeader"
}
}
}
}
});
app.addServices(discordService);
// Register escalation provider
app.waitForService(EscalationService, (escalationService) => {
escalationService.registerProvider(
"discordAdmins",
new DiscordEscalationProvider({
type: "discord",
bot: "primary",
channel: "general"
})
);
});
Agent Integration
Each Discord channel maintains its own agent instance:
- Agents are spawned on first message to a channel
- Agent context persists for the channel's lifetime
- Background event loops process agent outputs
- Agents are cleaned up on bot shutdown
Event Handling
The package handles Discord events:
messageCreate: Processes incoming messages- Bot mentions trigger agent interactions in guild channels
- Direct messages are handled separately
- Reply tracking enables escalation workflows
Gateway Intents
The Discord client uses the following Gateway Intents:
GatewayIntentBits.Guilds: Server/guild operationsGatewayIntentBits.GuildMessages: Guild message eventsGatewayIntentBits.MessageContent: Message content accessGatewayIntentBits.DirectMessages: Direct message events
Usage Examples
Basic Configuration
import { z } from "zod";
import TokenRingApp from "@tokenring-ai/app";
import discordPlugin from "@tokenring-ai/discord/plugin";
const config = {
discord: {
bots: {
primary: {
name: "Primary Bot",
botToken: process.env.DISCORD_BOT_TOKEN!,
joinMessage: "Discord bot is online and ready!",
maxFileSize: 20_971_520,
channels: {
engineering: {
channelId: "123456789012345678",
allowedUsers: [],
agentType: "teamLeader"
},
support: {
channelId: "987654321098765432",
allowedUsers: ["111111111111111111", "222222222222222222"],
agentType: "supportAgent"
}
},
dmAgentType: "personalAgent",
dmAllowedUsers: ["111111111111111111"]
}
}
}
};
const app = new TokenRingApp();
await app.installPlugin(discordPlugin, config);
Direct Message Setup
const config = {
discord: {
bots: {
main: {
name: "Main Bot",
botToken: process.env.DISCORD_BOT_TOKEN!,
channels: {
general: {
channelId: "123456789012345678",
allowedUsers: [],
agentType: "teamLeader"
}
},
dmAgentType: "personalAgent",
dmAllowedUsers: [
"111111111111111111",
"222222222222222222"
]
}
}
}
};
Escalation Integration
const config = {
discord: {
bots: {
adminBot: {
name: "Admin Bot",
botToken: process.env.DISCORD_BOT_TOKEN!,
channels: {
adminChannel: {
channelId: "123456789012345678",
allowedUsers: ["111111111111111111"],
agentType: "adminAgent"
}
}
}
}
},
escalation: {
providers: {
discordAdmins: {
type: "discord",
bot: "adminBot",
channel: "adminChannel"
}
}
}
};
Multiple Bots
const config = {
discord: {
bots: {
primary: {
name: "Primary Bot",
botToken: process.env.DISCORD_BOT_TOKEN_1!,
channels: {
engineering: {
channelId: "123456789012345678",
allowedUsers: [],
agentType: "teamLeader"
}
}
},
secondary: {
name: "Secondary Bot",
botToken: process.env.DISCORD_BOT_TOKEN_2!,
channels: {
support: {
channelId: "987654321098765432",
allowedUsers: [],
agentType: "supportAgent"
}
}
}
}
}
};
Best Practices
Bot Token Security
- Store tokens in environment variables
- Never commit tokens to version control
- Use different tokens for development and production
Channel Authorization
- Use
allowedUsersto restrict channel access - Empty
allowedUsersarray allows all users - Combine with Discord role-based permissions
Message Rate Limiting
- The package implements 250ms rate limiting automatically
- Large responses are automatically chunked
- Message edits are preferred over new messages
Attachment Handling
- Set appropriate
maxFileSizelimits - Monitor attachment processing errors
- Consider bandwidth implications for large files
Agent Configuration
- Use distinct
agentTypevalues per channel - Configure appropriate agent capabilities per use case
- Monitor agent resource usage for high-traffic channels
Escalation Setup
- Configure dedicated channels for escalation
- Use specific bot instances for admin communications
- Test escalation workflows before production use
Multi-bot Architecture
- Use separate bots for different purposes (e.g., primary, admin, support)
- Configure appropriate channels for each bot
- Monitor resource usage across multiple bots
Testing and Development
Running Tests
cd pkg/discord
bun test
Test Configuration
Tests use vitest for unit testing:
// vitest.config.ts
import { defineConfig } from 'vitest/config';
export default defineConfig({
test: {
environment: 'node',
globals: true
}
});
Example Test
import { describe, it, expect } from 'vitest';
describe('Discord Package', () => {
it('should export required components', () => {
expect(DiscordService).toBeDefined();
expect(DiscordEscalationProvider).toBeDefined();
expect(DiscordBotConfigSchema).toBeDefined();
});
});
Build
bun run build
Test Watch Mode
bun run test:watch
Test Coverage
bun run test:coverage
Dependencies
Runtime Dependencies
| Package | Version | Description |
|---|---|---|
@tokenring-ai/app | 0.2.0 | Base application framework |
@tokenring-ai/chat | 0.2.0 | Chat service |
@tokenring-ai/agent | 0.2.0 | Agent management |
@tokenring-ai/utility | 0.2.0 | Shared utilities |
@tokenring-ai/escalation | 0.2.0 | Escalation service |
discord.js | ^14.25.1 | Discord API client |
axios | ^1.13.6 | HTTP client for attachments |
zod | ^4.3.6 | Schema validation |
Dev Dependencies
| Package | Version | Description |
|---|---|---|
vitest | ^4.0.18 | Testing framework |
typescript | ^5.9.3 | TypeScript compiler |
Related Components
@tokenring-ai/escalation: Escalation service and provider interface@tokenring-ai/agent: Agent management and event handling@tokenring-ai/app: Base application framework@tokenring-ai/chat: Chat service for agent interactionsdiscord.js: Discord API client library
License
MIT License - see LICENSE file for details.