Skip to content

RFC: Gateway/Transport Listener Adapter Interface #178

@dcartertwo

Description

@dcartertwo

Problem Statement

The Chat SDK uses an adapter pattern for platforms (@chat-adapter/discord, @chat-adapter/slack) and state (Redis, Memory, community adapters). But the transport layer is hardcoded inside each platform adapter.

For Discord, startGatewayListener() requires discord.js and a long-running Node.js process. This makes it incompatible with all edge and non-Node runtimes (Vercel Edge Functions, Deno Deploy, Netlify Edge Functions, Fastly Compute, Supabase Edge Functions, Cloudflare Workers).

On Vercel Serverless Functions it works via a cron-restart pattern where each cycle creates a fresh discord.js client with an in-memory session store that's discarded on shutdown. Overlapping cycles can cause unpredictable handoff behavior since two clients compete for the same Gateway session.

This is the same problem described in #123 where the Slack adapter hardcodes HTTP webhook transport with no way to swap in Socket Mode. This required a patch of 200+ lines of compiled adapter output to make it work.
Discord has WebSocket vs. HTTP forwarding, Slack has HTTP webhooks vs. Socket Mode. Each time, the transport is locked in because there's no abstraction boundary between platform logic and event delivery.

Proposed Solution

Introduce a transport/listener adapter interface that platform adapters can accept, allowing the event delivery mechanism to be swapped independently of platform logic.

Platform adapters would accept an optional gatewayListener config. startGatewayListener() continues to work as it does today. The interface just allows alternatives for runtimes or deployment patterns where the built-in approach doesn't fit.

This follows the same pattern the SDK already uses for state adapters: a defined interface with a default implementation and community-contributed alternatives.

Alternatives Considered

The current workaround for any platform with a WebSocket-based event source is to independently implement the protocol outside the SDK and forward events in whatever format the adapter's handleWebhook() expects. For Discord, this means matching the GATEWAY_<EVENT> type prefix, x-discord-gateway-token header, and { type, timestamp, data } body shape (e.g. discord-gateway-cloudflare-do (https://github.com/dcartertwo/discord-gateway-cloudflare-do)). For Slack Socket Mode (#123), a community member patched 200+ lines of compiled adapter output to wire in @slack/socket-mode.

Each platform ends up with its own ad-hoc solution, coupled to undocumented internal details that could break with any adapter release. A transport adapter interface would replace these per-platform workarounds with a single extensibility pattern.

Use Case

import { Chat } from "chat";
import { createDiscordAdapter } from "@chat-adapter/discord";
import { createCustomGateway } from "some-community-package";
const bot = new Chat({
  userName: "my-bot",
  adapters: {
    discord: createDiscordAdapter({
      botToken: process.env.DISCORD_BOT_TOKEN,
      publicKey: process.env.DISCORD_PUBLIC_KEY,
      applicationId: process.env.DISCORD_APPLICATION_ID,
      // Pluggable transport — replaces startGatewayListener()
      gatewayListener: createCustomGateway({ /* runtime-specific config */ }),
    }),
  },
});
// Same bot logic — transport is transparent
bot.onNewMention(async (thread, message) => {
  await thread.post(`Hello! You said: ${message.text}`);
});

Priority

Nice to have

Contribution

  • I am willing to help implement this feature

Additional Context

Related issues:

Metadata

Metadata

Assignees

No one assigned

    Labels

    enhancementNew feature or request

    Type

    No type

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions