Skip to content

feat: Discord channel integration adapter (Slash Commands via Interactions API) #581

Description

@ginccc

Problem / Motivation

Discord (200M+ monthly active users) is a major platform for developer communities, open-source projects, and increasingly enterprise teams. EDDI's multi-agent group discussions map naturally to Discord's channel + thread model.

WARNING: Critical architectural difference from Slack: Discord's HTTP-based Interactions API only receives slash commands and component interactions (button clicks, select menus). Regular channel messages and thread replies require a persistent WebSocket Gateway connection. This fundamentally differs from Slack's pure HTTP Events API model. The recommended approach is a phased implementation.

Architecture Principle

No platform SDKs. All adapters use java.net.http.HttpClient + Jackson for raw HTTP/JSON - no JDA, no Discord4J. Every messaging platform's API is just REST under the hood. Raw HTTP keeps the dependency tree lean, the single-JAR deployment simple, and gives full control over retry logic, error handling, and message formatting. See the existing SlackWebApiClient (245 lines, zero external dependencies) as the reference pattern.

Proposed Solution

Phase 1 - Slash Commands via Interactions API (HTTP)

MVP that matches EDDI's existing HTTP webhook architecture:

Class Responsibility Slack Equivalent
RestDiscordWebhook JAX-RS @POST /integrations/discord/interactions. Verifies Ed25519 signature, handles Discord's ping verification, dispatches interactions async. RestSlackWebhook
DiscordSignatureVerifier Ed25519 signature verification using Discord's public key. Uses JDK's java.security.Signature.getInstance("Ed25519") (available since JDK 15; EDDI runs on JDK 25). Requires reconstructing the public key from Discord's hex-encoded publicKey. SlackSignatureVerifier
DiscordEventHandler Routes Discord slash command interactions to EDDI via ChannelTargetRouter. Creates threads for responses. Maps Discord interactions -> EDDI conversations. SlackEventHandler
DiscordApiClient java.net.http.HttpClient-based client calling Discord REST API (/channels/{id}/messages, /interactions/{id}/{token}/callback). Standard Markdown (Discord's native format). Handles X-RateLimit-* headers. SlackWebApiClient
DiscordGroupDiscussionListener Implements GroupDiscussionEventListener. Creates a thread for each group discussion, posts agent contributions as thread messages. SlackGroupDiscussionListener
DiscordDeliveryException Retryable delivery failure. SlackDeliveryException

Phase 1 limitations:

  • Users must invoke via /eddi <message> slash command (no @mention in regular messages)
  • Thread follow-ups require the user to use the slash command again within the thread
  • No ambient channel listening (proactive responses, observe mode)

Phase 2 - Gateway for Ambient Listening (Future Enhancement)

Add WebSocket Gateway connection for MESSAGE_CREATE events:

  • Enables @mention triggering (like Slack's app_mention)
  • Enables thread reply continuity without slash commands
  • Requires: heartbeat management, reconnection logic, intent subscriptions (MESSAGE_CONTENT privileged intent)
  • This is a significant architecture extension and should be a separate follow-up issue

Configuration Model

{
  "name": "Dev Community AI Channel",
  "channelType": "discord",
  "platformConfig": {
    "guildId": "123456789",
    "channelId": "987654321",
    "botToken": "${vault:discord-bot-token}",
    "publicKey": "${vault:discord-public-key}",
    "applicationId": "111222333"
  },
  "defaultTargetName": "assistant",
  "targets": [...]
}

Key Design Decisions

  1. Phase 1 = HTTP only - No WebSocket Gateway in the initial implementation. Keeps the adapter architecturally consistent with Slack's pure-HTTP model. Gateway support is a natural Phase 2.
  2. Slash command registration - Provide documentation for registering slash commands via Discord's API (PUT /applications/{id}/commands). Consider a setup MCP tool or REST endpoint.
  3. Deferred response pattern - Discord requires initial interaction acknowledgment within 3 seconds (like Slack). Use DEFERRED_CHANNEL_MESSAGE_WITH_SOURCE (type 5) to show "thinking...", then follow up with the actual response via webhook.
  4. Threads for group discussions - Discord threads map well to the Slack pattern. Each agent's contribution -> thread message.
  5. Ed25519 verification - More involved than Slack's HMAC-SHA256: requires hex-decoding the public key, constructing an EdDSAPublicKey, and verifying the signature of timestamp + body. JDK 25 supports this natively via java.security.Signature - no external libraries needed.
  6. Rate limits - Discord uses per-route rate limiting with X-RateLimit-* response headers. Implement header-driven pacing (respect X-RateLimit-Remaining and X-RateLimit-Reset-After). Channel message sending: 5 requests per 5 seconds per channel.
  7. Message size limit - 2000 characters (smallest of all platforms). Implement aggressive line-break-aware chunking.
  8. Loop prevention - Filter interactions with member.user.bot == true; Discord slash command interactions are always from users (bots can't invoke slash commands), but component interactions need filtering.

Deliverables

  • 6 Java classes in ai.labs.eddi.integrations.discord (Phase 1 scope)
  • Unit tests for all classes
  • Add "discord" to REGISTERED_CHANNEL_TYPES
  • Documentation page: docs/discord-integration.md
  • Slash command registration guide in docs

Alternatives Considered

  • JDA (Java Discord API) - Full-featured library but forces WebSocket Gateway model and adds a large dependency tree. Raw HTTP keeps EDDI lightweight and consistent with the no-SDK principle.
  • Gateway-first approach - More feature-complete but architecturally divergent from EDDI's HTTP webhook model. Phased approach is lower risk.

Additional Context

Acceptance Criteria

  • All classes listed in Deliverables are implemented and compile without warnings
  • Unit tests for every class (following SlackEventHandlerTest pattern - pure unit tests, no CDI container)
  • "discord" added to REGISTERED_CHANNEL_TYPES in RestChannelIntegrationStore
  • ./mvnw test passes with zero failures
  • Code coverage meets project targets: >90% instruction coverage, >80% branch coverage (measured by JaCoCo)
  • Documentation page docs/discord-integration.md exists (following docs/slack-integration.md structure)
  • Slash command registration guide included in docs
  • PR description includes screenshots or curl transcripts demonstrating:
    • Slash command interaction received and routed to an EDDI agent
    • Deferred response (DEFERRED_CHANNEL_MESSAGE_WITH_SOURCE) followed by agent reply
    • Ed25519 signature verification rejecting an invalid request
  • PR submitted for review - do NOT merge without approval

Metadata

Metadata

Assignees

No one assigned

    Labels

    enhancementhelp wantedintegrationChannel integration adapters (Slack, Teams, Discord, Telegram, WhatsApp)javaPull requests that update java codesize: LLarge: full day+, significant scope, design discussion likely

    Type

    No type

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions