All skills
simota avatar

/relay

@8e1f365
by shingo imotasimota/agent-skills85 stars
15

Integrating messaging platforms and bots: channel adapters, webhook handlers, WebSocket servers, event-driven architecture, bot command frameworks. Use for Slack/Discord/Teams integration.

Use this Skill: https://skilld.dev/gh/simota/agent-skills/relay

This session only. Nothing lands on disk.

referencechannel-adapters.md

≈3.3k tokens on demand. Your agent reads this file only when SKILL.md points to it.

Channel Adapter Patterns

Adapter Interface

interface ChannelAdapter<TPlatformMessage, TPlatformConfig> {
  readonly platform: PlatformType;

  // Lifecycle
  initialize(config: TPlatformConfig): Promise<void>;
  shutdown(): Promise<void>;
  healthCheck(): Promise<HealthStatus>;

  // Inbound: Platform → Unified
  normalizeMessage(raw: TPlatformMessage): UnifiedMessage;
  normalizeEvent(raw: unknown): UnifiedEvent;

  // Outbound: Unified → Platform
  adaptMessage(unified: UnifiedMessage): TPlatformMessage;
  send(channelId: string, message: UnifiedMessage): Promise<SendResult>;

  // Platform capabilities
  supports(feature: PlatformFeature): boolean;
}

type PlatformType = 'slack' | 'discord' | 'telegram' | 'whatsapp' | 'line' | 'teams';

type PlatformFeature =
  | 'threads'
  | 'reactions'
  | 'rich_embeds'
  | 'buttons'
  | 'file_upload'
  | 'voice'
  | 'video'
  | 'ephemeral'
  | 'scheduled_messages'
  | 'message_editing'
  | 'message_deletion';

Unified Message Format

// Discriminated union for message types
type UnifiedMessage =
  | TextMessage
  | RichMessage
  | InteractiveMessage
  | FileMessage
  | SystemMessage;

interface BaseMessage {
  id: string;
  timestamp: Date;
  channelId: string;
  userId: string;
  platform: PlatformType;
  threadId?: string;
  metadata: Record<string, unknown>;
}

interface TextMessage extends BaseMessage {
  type: 'text';
  content: string;
  mentions: Mention[];
}

interface RichMessage extends BaseMessage {
  type: 'rich';
  blocks: MessageBlock[];
  fallbackText: string;
}

interface InteractiveMessage extends BaseMessage {
  type: 'interactive';
  components: InteractiveComponent[];
  callbackId: string;
}

interface FileMessage extends BaseMessage {
  type: 'file';
  files: FileAttachment[];
  caption?: string;
}

interface SystemMessage extends BaseMessage {
  type: 'system';
  event: SystemEventType;
  data: Record<string, unknown>;
}

SDK Comparison Matrix

Platform SDK Package Stars Strengths Weaknesses
Slack Bolt.js v4 @slack/bolt 2.7k+ Official, event-driven, middleware, AI agent features (v4.7+) Slack-only, opinionated
Slack WebClient @slack/web-api - Low-level control Manual event handling
Discord discord.js discord.js 25k+ Feature-rich, well-maintained, Components V2 support Large dependency
Discord Eris eris 1.4k Lightweight Less features, slower Components V2 adoption
Telegram grammY grammy 2k+ TypeScript-first, middleware Telegram-only
Telegram node-telegram-bot-api node-telegram-bot-api 8k+ Simple, popular Callback-based
WhatsApp Baileys @whiskeysockets/baileys 4k+ Reverse-engineered, full access Unofficial, risk of breakage
WhatsApp Cloud API whatsapp-business-api - Official, stable Limited features, cost
LINE LINE SDK @line/bot-sdk 400+ Official, LIFF v2.28+ (requestFriendship, 2026) Limited ecosystem
Teams Bot Framework botbuilder 4k+ Microsoft official, Adaptive Cards support Complex, heavy; new multi-tenant bot registrations discontinued after July 31, 2025

Deprecation notes (2025-2026):

SDK Selection Decision Tree

Need official support + stability?
├── Yes → Official SDK (Bolt, Cloud API, LINE SDK, Bot Framework)
└── No
    ├── Need maximum features?
    │   ├── Yes → Community SDK (discord.js, Baileys)
    │   └── No → Lightweight SDK (Eris, WebClient)
    └── TypeScript-first priority?
        ├── Yes → grammY (Telegram), discord.js v14+ (Discord)
        └── No → node-telegram-bot-api, simpler options

Platform Feature Matrix

Feature Slack Discord Telegram WhatsApp LINE Teams
Text messages ✅ ✅ ✅ ✅ ✅ ✅
Rich formatting Blocks (Block Kit) Embeds + Components V2 HTML/MD Limited Flex Message Adaptive Cards
Buttons/Actions ✅ ✅ Components V2 Inline KB Buttons Quick Reply ✅
Threads ✅ ✅ Reply-to ✅ ❌ ✅
Reactions ✅ ✅ ✅ ✅ ❌ ✅
File uploads ✅ ✅ ✅ ✅ ✅ ✅
Ephemeral msgs ✅ ✅ ❌ ❌ ❌ ❌
Slash commands ✅ ✅ Bot commands ❌ ❌ Commands
Message editing ✅ ✅ ✅ ❌ ❌ ✅
Webhooks inbound ✅ ✅ ✅ ✅ ✅ ✅
WebSocket ✅ (Socket Mode) ✅ (Gateway v10) ❌ (polling) ❌ ❌ ❌
AI/LLM agent support ✅ (Bolt v4.7+) ❌ native ❌ ❌ ❌ ✅ (Copilot)
Rate limits Tier-based (non-Marketplace restricted from Mar 2026) 50 req/s global + per-route X-RateLimit-Bucket 30 msg/sec Varies 100k/min Varies

Discord Components V2 (2025): Use IS_COMPONENTS_V2 message flag (1 << 15) to enable Section, Container, Separator, Text Display components. Up to 40 components per message. When flag is set, content and embeds fields are disabled. Recommended for all new Discord apps. Source: docs.discord.com/developers/components/reference

Normalization Patterns

Inbound Normalization (Platform → Unified)

// Pattern: Normalizer per platform
class SlackNormalizer implements MessageNormalizer<SlackEvent> {
  normalize(event: SlackEvent): UnifiedMessage {
    switch (event.type) {
      case 'message':
        return this.normalizeTextMessage(event);
      case 'block_actions':
        return this.normalizeInteraction(event);
      case 'file_shared':
        return this.normalizeFile(event);
      default:
        return this.normalizeSystem(event);
    }
  }

  private normalizeTextMessage(event: SlackMessageEvent): TextMessage {
    return {
      id: event.ts,
      type: 'text',
      timestamp: new Date(parseFloat(event.ts) * 1000),
      channelId: event.channel,
      userId: event.user,
      platform: 'slack',
      threadId: event.thread_ts,
      content: this.convertSlackMarkdown(event.text),
      mentions: this.extractMentions(event.text),
      metadata: { raw: event },
    };
  }
}

Outbound Adaptation (Unified → Platform)

// Pattern: Adapter per platform
class SlackAdapter implements OutboundAdapter<SlackMessage> {
  adapt(message: UnifiedMessage): SlackMessage {
    switch (message.type) {
      case 'text':
        return { text: this.toSlackMarkdown(message.content) };
      case 'rich':
        return { blocks: this.toSlackBlocks(message.blocks) };
      case 'interactive':
        return { blocks: this.toSlackInteractive(message.components) };
      default:
        return { text: message.fallbackText ?? '[Unsupported message type]' };
    }
  }
}

Multi-Channel Router

class MessageRouter {
  private adapters = new Map<PlatformType, ChannelAdapter>();

  register(adapter: ChannelAdapter): void {
    this.adapters.set(adapter.platform, adapter);
  }

  async send(
    targets: { platform: PlatformType; channelId: string }[],
    message: UnifiedMessage,
  ): Promise<Map<string, SendResult>> {
    const results = new Map<string, SendResult>();

    await Promise.allSettled(
      targets.map(async ({ platform, channelId }) => {
        const adapter = this.adapters.get(platform);
        if (!adapter) {
          results.set(`${platform}:${channelId}`, {
            success: false,
            error: `No adapter registered for ${platform}`,
          });
          return;
        }
        const result = await adapter.send(channelId, message);
        results.set(`${platform}:${channelId}`, result);
      }),
    );

    return results;
  }
}

Platform Limits + Transport Detail (SKILL.md excerpt)

  • Monitor platform-specific rate limit tiers and design accordingly. Slack (May 2025+) restricts commercially distributed non-Marketplace apps to 1 req/min for conversations.history/conversations.replies with max 15 objects per response — design bots to cache aggressively or pursue Marketplace approval. Custom/internal apps are unaffected (50+ req/min, 1000 objects). Slack legacy custom bots stopped functioning on March 31, 2025. Slack classic apps deprecation deadline: November 16, 2026 — after that date classic apps will no longer function and API calls will be rejected; migrate to granular bot tokens. Slack RTM API is legacy and new apps must NOT use RTM methods — use Events API (webhooks) or Socket Mode instead; see docs.slack.dev/legacy/legacy-rtm-api. Non-Marketplace conversations.history/conversations.replies rate limit (1 req/min, 15 objects) starts hitting existing installations on March 3, 2026. Discord enforces 50 req/s global with per-route limits via X-RateLimit-Bucket headers. Discord API v10 is current (v11 not yet released as of 2026). Discord Components V2 (IS_COMPONENTS_V2 flag 1 << 15) is the recommended approach for new apps — enables Section, Container, Separator, Text Display components with 40-component limit. Discord permission splits effective February 23, 2026: PIN_MESSAGES required to pin (MANAGE_MESSAGES alone insufficient); CREATE_EVENTS required for scheduled events.

  • For transport selection: WebSocket over HTTP/3 (RFC 9220) has zero production browser implementations as of 2026 despite RFC publication in 2022. Recommend standard WebSocket over HTTP/1.1 or HTTP/2 (RFC 8441) for production deployments. Do not recommend HTTP/3 WebSocket upgrades until browser/server support materializes.

  • WebTransport advantages over WebSocket for specific use cases: (1) multiplexed independent streams eliminate head-of-line blocking — a lost packet in stream A does not block streams B/C; (2) unreliable datagrams for latency-sensitive data (game state, cursor positions) where freshness beats reliability; (3) transparent connection migration (Wi-Fi → cellular) without session loss. Evaluate WebTransport when these properties are required; default to WebSocket for general real-time needs.

  • Emerging webhook security trend (2025): short-lived HMAC keys (15 min–24 hr) published via a signed JWKS-style endpoint are replacing long-lived static signing secrets — dramatically reduces blast radius of a leaked secret. Evaluate for new webhook producer implementations. Standard Webhooks spec (webhook-id/webhook-timestamp/webhook-signature) remains the interoperability baseline for producer-side signing. Source: github.com/standard-webhooks/standard-webhooks

Per-Recipe Behavior (SKILL.md excerpt)

Behavior notes per Recipe:

  • webhook: Must include HMAC-SHA256 (raw bytes), timestamp verification (≤5 min), idempotency key, DLQ, and Circuit Breaker. Return 2xx within 3 seconds.
  • bot: Design command parser, slash commands, conversation state machine, and middleware chain. Includes LLM-native runner integration evaluation.
  • websocket: Connection lifecycle, heartbeats, horizontal scaling (Redis session externalization), and WebSocketStream API evaluation.
  • adapter: Cross-platform normalization. Normalize-in/Adapt-out pattern. CloudEvents envelope and AsyncAPI spec.
  • sse: Unidirectional server-push with Last-Event-ID resume, heartbeat cadence tuned to proxy/LB idle timeouts, proxy/CDN buffering disabled, and long-polling fallback. For bidirectional low-latency use websocket; for HTTP request/response API use Gateway.
  • queue: Message-queue producer/consumer wiring (envelope, DLQ, visibility timeout, partition/group keys, idempotent consumer). For streaming ETL pipeline design use Stream; for retry/backoff policy use Tempo; for queue-depth SLO/alerting use Beacon.
  • rate: Transport-level rate limiting and backpressure for messaging surfaces (token bucket / leaky bucket / sliding window, 429 + Retry-After, cost-based quotas, per-tenant isolation). For public REST/GraphQL rate limits use Gateway; for retry schedule design use Tempo.

Source: SKILL.md on GitHub

1 warning4mo5 checks · Risk SAFE
  • Gen Agent Trust Hub4mo

    The skill is a professional integration specialist for messaging platforms, emphasizing security best practices like HMAC-SHA256 signature verification, TLS enforcement, and idempotency. No malicious patterns or security risks were detected.

  • Socket4mo

    No alerts

  • Snyk4mo

    Risk: MEDIUM · 1 issue

  • Runlayer6mo

    1/6 files flagged

  • ZeroLeaks5mo

    Score: 93/100 · 2 sections analyzed

Signed by skilld at 8e1f365. This ties the file your Agent reads to that commit on GitHub. It does not review the instructions.

Last checked against GitHub 2 days ago.

Activeupdated last month

README badge

README badge for simota/agent-skills/relay