Chuyển đến nội dung chính

Lesson 18: Chat Channels — Multi-platform Messaging

Channel adapter pattern: Web Chat, Telegram, Slack, Discord, Zalo, Facebook Messenger, LINE, WhatsApp. Unified message format, channel-specific features, webhook handling.

🧠 AI & ML — Lesson 17 Lesson 18: Chat Channels — Multi-platform Messaging

Building AI Agent Platform from Zero — Real battle with xClaw

Part 6: Multi-tenant, RBAC & Channels

xdev.asia

Introduction

AI agents are only useful when users can access them from familiar platforms. Chat Channels allow agents to be deployed to Telegram, Slack, Discord, and many other platforms — all using the same Agent core.


1. Channel Adapter Interface

// packages/channels/src/types.ts
export interface ChannelAdapter {
  readonly channelType: string;

  // Initialize channel (set up webhooks, etc.)
  initialize(config: ChannelConfig): Promise<void>;

  // Send message to channel
  sendMessage(channelId: string, message: ChannelMessage): Promise<void>;

  // Parse incoming webhook to unified format
  parseIncoming(rawPayload: unknown): IncomingMessage;

  // Cleanup
  shutdown(): Promise<void>;
}

export interface ChannelMessage {
  text: string;
  markdown?: string;
  images?: string[];
  buttons?: { label: string; action: string }[];
  metadata?: Record<string, unknown>;
}

export interface IncomingMessage {
  channelType: string;
  channelId: string;        // channel/group ID
  userId: string;           // user ID on the platform
  userName: string;
  text: string;
  images?: string[];
  replyToMessageId?: string;
  raw: unknown;             // Original payload
}

export interface ChannelConfig {
  token: string;
  webhookUrl?: string;
  additionalConfig?: Record<string, unknown>;
}

2. Telegram Channel

// packages/channels/telegram/src/telegram-adapter.ts
export class TelegramAdapter implements ChannelAdapter {
  readonly channelType = 'telegram';
  private bot: TelegramBot;

  async initialize(config: ChannelConfig) {
    this.bot = new TelegramBot(config.token);

    if (config.webhookUrl) {
      await this.bot.setWebhook(config.webhookUrl);
    }
  }

  parseIncoming(payload: unknown): IncomingMessage {
    const update = payload as TelegramUpdate;
    const msg = update.message!;

    return {
      channelType: 'telegram',
      channelId: String(msg.chat.id),
      userId: String(msg.from!.id),
      userName: msg.from!.first_name,
      text: msg.text || '',
      images: msg.photo?.map(p => p.file_id),
      replyToMessageId: msg.reply_to_message
        ? String(msg.reply_to_message.message_id)
        : undefined,
      raw: payload,
    };
  }

  async sendMessage(chatId: string, message: ChannelMessage) {
    if (message.markdown) {
      await this.bot.sendMessage(chatId, message.markdown, {
        parse_mode: 'MarkdownV2',
      });
    } else {
      await this.bot.sendMessage(chatId, message.text);
    }

    // Send images if any
    if (message.images) {
      for (const image of message.images) {
        await this.bot.sendPhoto(chatId, image);
      }
    }
  }

  async shutdown() {
    await this.bot.deleteWebhook();
  }
}

3. Channel Routers

// packages/gateway/src/channels/channel-router.ts
export class ChannelRouter {
  private channels = new Map<string, ChannelAdapter>();
  private agent: Agent;

  register(adapter: ChannelAdapter) {
    this.channels.set(adapter.channelType, adapter);
  }

  // Webhook handler — receives messages from all channels
  async handleWebhook(channelType: string, payload: unknown): Promise<void> {
    const adapter = this.channels.get(channelType);
    if (!adapter) throw new Error(`Unknown channel: ${channelType}`);

    // Parse to unified format
    const incoming = adapter.parseIncoming(payload);

    // Find or create user mapping
    const user = await this.resolveUser(incoming);

    // Chat with agent
    const response = await this.agent.chat(
      incoming.text,
      {
        tenantId: user.tenantId,
        userId: user.id,
        sessionId: `${channelType}:${incoming.channelId}`,
      },
    );

    // Send response back through the same channel
    await adapter.sendMessage(incoming.channelId, {
      text: response,
      markdown: response,
    });
  }
}

// Webhook routes
app.post('/webhooks/telegram', async (c) => {
  const payload = await c.req.json();
  await channelRouter.handleWebhook('telegram', payload);
  return c.json({ ok: true });
});

app.post('/webhooks/slack', async (c) => {
  const payload = await c.req.json();
  await channelRouter.handleWebhook('slack', payload);
  return c.json({ ok: true });
});

4. Supported Channels

ChannelFeaturesAdapters
Web ChatFull rich UI, streaming, file uploadBuilt-in React app
TelegramMarkdown, inline buttons, groupsBot API + webhooks
SlackThreads, reactions, slash commandsEvents API
DiscordEmbeds, threads, slash commandsDiscord.js
ZaloVietnamese messaging, OA APIZalo OA API
Facebook MessengerQuick replies, templatesGraph API
LINERich menus, flex messagesMessaging API
WhatsAppTemplates, media, business APICloud API

5. Summary

  • Adapter Pattern — unified interface for all platforms
  • Channel Router — webhook gateway → parse → agent.chat() → send response
  • User mapping — external platform user → internal user
  • One agent, many channels — same AI, different frontends

Next article: React Frontend — Build the web chat interface.