# AionUi Channels Subsystem Architecture: Telegram, Lark, and DingTalk Integration Guide

> Explore AionUi Channels subsystem architecture for Telegram Lark DingTalk integration. Learn how platform specific plugins and adapters create a unified message model for seamless communication. Get the guide now!

- Repository: [OfficeAI/AionUi](https://github.com/iofficeai/aionui)
- Tags: architecture
- Published: 2026-02-19

---

**AionUi's Channels subsystem implements a plugin-based architecture where a central ChannelManager orchestrates platform-specific plugins that convert native IM payloads to a unified message model via adapters, enabling seamless integration with Telegram, Lark, and DingTalk without modifying core assistant logic.**

The Channels subsystem in the iOfficeAI/AionUi repository serves as the communication bridge between the AI assistant and external instant-messaging platforms. By abstracting platform-specific protocols through a unified plugin architecture, developers can add, remove, or modify Telegram, Lark, and DingTalk integrations independently of the core application logic.

## Core Architectural Components

The subsystem centers around five primary components that manage the lifecycle, routing, and conversion of messages between the AI core and external platforms.

### ChannelManager Singleton

Located at [`src/channels/core/ChannelManager.ts`](https://github.com/iOfficeAI/AionUi/blob/main/src/channels/core/ChannelManager.ts), the **ChannelManager** acts as the central orchestrator. It initializes as a singleton on application startup and creates three critical sub-components: the **PluginManager**, **SessionManager**, and **PairingService**. The manager loads enabled plugins from the database via `loadEnabledPlugins`, validates their configurations, and exposes helper methods like `getActiveUserCount` and `getBotInfo` for monitoring channel health.

### PluginManager Registry

The **PluginManager** ([`src/channels/gateway/PluginManager.ts`](https://github.com/iOfficeAI/AionUi/blob/main/src/channels/gateway/PluginManager.ts)) maintains a registry of concrete platform plugins and handles their runtime lifecycle. It stores plugin constructors, tracks active instances, and captures runtime errors. Crucially, it injects the unified message handler and tool-confirmation handler into each plugin via `setMessageHandler` and `setConfirmHandler`, ensuring all platforms route incoming traffic through the same processing pipeline.

### BasePlugin Abstraction

All platform implementations extend **BasePlugin** ([`src/channels/plugins/BasePlugin.ts`](https://github.com/iOfficeAI/AionUi/blob/main/src/channels/plugins/BasePlugin.ts)), an abstract base class providing a state machine for lifecycle management, status logging, and callback registration. Concrete plugins must implement three core methods: `onInitialize` for credential validation and SDK instantiation, `onStart` for establishing connections, and `onStop` for resource cleanup. The base class also provides `emitMessage`, which forwards unified messages to the handler injected by PluginManager.

### ActionExecutor Bridge

The **ActionExecutor** ([`src/channels/gateway/ActionExecutor.ts`](https://github.com/iOfficeAI/AionUi/blob/main/src/channels/gateway/ActionExecutor.ts)) serves as the bridge between the Channels subsystem and the AI assistant core. It transforms unified inbound messages into assistant actions, manages tool call confirmations, and routes assistant responses back to the originating plugin. This component plugs into the PluginManager via the `setMessageHandler` interface, ensuring decoupled message processing.

### PairingService Authentication

Located at [`src/channels/pairing/PairingService.ts`](https://github.com/iOfficeAI/AionUi/blob/main/src/channels/pairing/PairingService.ts), the **PairingService** manages the one-time pairing code workflow that links a user's native platform account (Telegram, Lark, or DingTalk) to an internal AionUi session. It generates short-lived codes via `createCode` and validates them when users initiate contact through `/start` commands or equivalent entry points.

## The Unified Message Model and Adapter Pattern

To handle platform heterogeneity, the subsystem implements a **Unified Message Model** defined in [`src/channels/types.ts`](https://github.com/iOfficeAI/AionUi/blob/main/src/channels/types.ts). This interface includes `IUnifiedIncomingMessage` and `IUnifiedOutgoingMessage`, standardizing fields like content type, sender identity, and conversation context across all platforms.

Each platform plugin includes a dedicated **Adapter** class that handles bidirectional conversion:

- **TelegramAdapter** ([`src/channels/plugins/telegram/TelegramAdapter.ts`](https://github.com/iOfficeAI/AionUi/blob/main/src/channels/plugins/telegram/TelegramAdapter.ts)): Converts grammy SDK payloads to unified messages, handles Markdown conversion, and manages Telegram's message length limits.
- **LarkAdapter** ([`src/channels/plugins/lark/LarkAdapter.ts`](https://github.com/iOfficeAI/AionUi/blob/main/src/channels/plugins/lark/LarkAdapter.ts)): Maps between Lark's interactive card structures and unified text/markdown content.
- **DingTalkAdapter** ([`src/channels/plugins/dingtalk/DingTalkAdapter.ts`](https://github.com/iOfficeAI/AionUi/blob/main/src/channels/plugins/dingtalk/DingTalkAdapter.ts)): Handles DingTalk's streaming card formats and fallback REST API payloads.

These adapters ensure that the ActionExecutor and AI core receive consistent message structures regardless of the originating platform's native format.

## Message Routing Flow

### Inbound Message Processing

When a user sends a message to the bot, the flow proceeds through these stages:

1. **Platform SDK receives payload** – For Telegram, the grammy `Bot` receives the update via long-polling; Lark uses a persistent WebSocket; DingTalk processes streaming card callbacks.
2. **Plugin converts to unified format** – The platform-specific plugin (e.g., `TelegramPlugin`) uses its adapter to transform the payload into an `IUnifiedIncomingMessage`.
3. **Emit to core** – The plugin calls `emitMessage` (inherited from `BasePlugin`), which invokes the handler set by PluginManager.
4. **ActionExecutor processes** – The handler routes to `ActionExecutor`, which identifies the user session, invokes the AI agent, and generates a response.
5. **Response adaptation** – The ActionExecutor passes the `IUnifiedOutgoingMessage` back to the originating plugin's `sendMessage` method.

### Outbound Delivery

The `sendMessage` implementation in each plugin (e.g., `TelegramPlugin.sendMessage`) converts the unified outgoing message back to platform-specific parameters. For Telegram, this means calling `toTelegramSendParams` to handle text splitting and Markdown parsing before invoking `bot.api.sendMessage`. Lark and DingTalk plugins construct interactive card JSON structures appropriate to their respective APIs.

## Platform-Specific Connection Modes

While the architecture abstracts common functionality, each platform plugin implements distinct connection strategies based on the underlying service's capabilities:

### Telegram: Long-Polling

The `TelegramPlugin` ([`src/channels/plugins/telegram/TelegramPlugin.ts`](https://github.com/iOfficeAI/AionUi/blob/main/src/channels/plugins/telegram/TelegramPlugin.ts)) utilizes the grammy SDK with a long-polling strategy. During `onStart`, it validates the bot token via `bot.api.getMe()` before invoking `startPolling`. Incoming `/start` commands trigger `handleStartCommand`, which initiates the PairingService workflow to generate linking codes for new users.

### Lark: WebSocket Event Streaming

`LarkPlugin` ([`src/channels/plugins/lark/LarkPlugin.ts`](https://github.com/iOfficeAI/AionUi/blob/main/src/channels/plugins/lark/LarkPlugin.ts)) establishes a persistent WebSocket connection to Lark/Feishu servers. It handles event deduplication and dispatches messages to the adapter for conversion. Unlike Telegram's text-focused approach, Lark integration emphasizes interactive cards, requiring the adapter to map between card JSON structures and unified message content.

### DingTalk: Streaming Cards with REST Fallback

The `DingTalkPlugin` ([`src/channels/plugins/dingtalk/DingTalkPlugin.ts`](https://github.com/iOfficeAI/AionUi/blob/main/src/channels/plugins/dingtalk/DingTalkPlugin.ts)) implements a hybrid connection model using streaming card callbacks for real-time interactions, with a fallback to REST APIs for message delivery. This plugin requires `clientId` and `clientSecret` credentials (distinct from Lark's `appId`/`appSecret` or Telegram's single token), and constructs complex card layouts for rich interactive responses.

## Implementation Examples

### Booting the Channels Subsystem

To initialize all enabled platform integrations:

```typescript
import { ChannelManager } from '@/channels/core/ChannelManager';

async function bootChannels() {
  // Initialize singleton and start all enabled platform plugins
  await ChannelManager.getInstance().initialize();
}

bootChannels().catch(console.error);

```

*Source:* [`src/channels/core/ChannelManager.ts`](https://github.com/iOfficeAI/AionUi/blob/main/src/channels/core/ChannelManager.ts)

### Creating a Custom Platform Plugin

Developers can extend the system by subclassing `BasePlugin`:

```typescript
// src/channels/plugins/example/ExamplePlugin.ts
import { BasePlugin } from '../BasePlugin';
import type { IUnifiedOutgoingMessage, PluginType, IChannelPluginConfig } from '@/channels/types';

export class ExamplePlugin extends BasePlugin {
  readonly type: PluginType = 'example';

  protected async onInitialize(config: IChannelPluginConfig): Promise<void> {
    // Validate config.credentials and instantiate SDK client
  }

  protected async onStart(): Promise<void> {
    // Open WebSocket or start polling loop
  }

  protected async onStop(): Promise<void> {
    // Clean up connections and timers
  }

  async sendMessage(chatId: string, message: IUnifiedOutgoingMessage): Promise<string> {
    // Convert message via adapter and dispatch to platform API
    return 'platform-message-id';
  }
}

```

Register the plugin in `ChannelManager`:

```typescript
import { ExamplePlugin } from '@/channels/plugins/example/ExamplePlugin';
// Within ChannelManager initialization:
registerPlugin('example', ExamplePlugin);

```

### Handling User Pairing Codes

When a user initiates contact via `/start` on Telegram (or equivalent on other platforms), the plugin generates a pairing code:

```typescript
// Inside TelegramPlugin.handleStartCommand
private async handleStartCommand(ctx: Context): Promise<void> {
  // Generate short-lived pairing code linked to platform user ID
  const code = this.pairingService?.createCode(ctx.from.id.toString(), 'telegram');
  if (!code) throw new Error('Pairing service unavailable');

  await ctx.reply(`Your pairing code is: ${code}\nEnter it in AionUi Settings → Channels → Telegram`);
}

```

*Sources:* [`src/channels/pairing/PairingService.ts`](https://github.com/iOfficeAI/AionUi/blob/main/src/channels/pairing/PairingService.ts), [`src/channels/plugins/telegram/TelegramPlugin.ts`](https://github.com/iOfficeAI/AionUi/blob/main/src/channels/plugins/telegram/TelegramPlugin.ts)

## Summary

- **ChannelManager** ([`src/channels/core/ChannelManager.ts`](https://github.com/iOfficeAI/AionUi/blob/main/src/channels/core/ChannelManager.ts)) serves as the singleton orchestrator, loading plugins and wiring message handlers during application startup.
- **PluginManager** ([`src/channels/gateway/PluginManager.ts`](https://github.com/iOfficeAI/AionUi/blob/main/src/channels/gateway/PluginManager.ts)) maintains the registry of platform implementations and injects routing handlers into each plugin instance.
- **BasePlugin** ([`src/channels/plugins/BasePlugin.ts`](https://github.com/iOfficeAI/AionUi/blob/main/src/channels/plugins/BasePlugin.ts)) provides the lifecycle abstraction that all platform plugins must extend, standardizing initialization, startup, and shutdown sequences.
- **Adapters** (e.g., [`TelegramAdapter.ts`](https://github.com/iOfficeAI/AionUi/blob/main/TelegramAdapter.ts), [`LarkAdapter.ts`](https://github.com/iOfficeAI/AionUi/blob/main/LarkAdapter.ts), [`DingTalkAdapter.ts`](https://github.com/iOfficeAI/AionUi/blob/main/DingTalkAdapter.ts)) convert between platform-specific SDK payloads and the **Unified Message Model** (`IUnifiedIncomingMessage`/`IUnifiedOutgoingMessage`).
- **ActionExecutor** ([`src/channels/gateway/ActionExecutor.ts`](https://github.com/iOfficeAI/AionUi/blob/main/src/channels/gateway/ActionExecutor.ts)) bridges unified messages to the AI assistant core and routes responses back to the appropriate platform plugin.
- **PairingService** ([`src/channels/pairing/PairingService.ts`](https://github.com/iOfficeAI/AionUi/blob/main/src/channels/pairing/PairingService.ts)) manages authentication workflows, generating short-lived codes to link external platform identities to internal user sessions.

## Frequently Asked Questions

### How does AionUi handle different credential schemes across Telegram, Lark, and DingTalk?

Each platform plugin validates its specific credential set during the `onInitialize` phase. **TelegramPlugin** requires a single `token` string for the grammy Bot instance. **LarkPlugin** expects `appId` and `appSecret` for WebSocket authentication. **DingTalkPlugin** uses `clientId` and `clientSecret` for its streaming card and REST API access. These credentials are stored in the database as JSON within `IChannelPluginConfig` and validated before the connection is established.

### What happens when a platform's message format differs significantly from text-only chat?

The **Adapter** pattern handles format disparities. While Telegram uses plain text with optional inline keyboards, Lark and DingTalk rely on interactive card structures (JSON payloads). The respective adapters—`LarkAdapter` and `DingTalkAdapter`—map between these card formats and the unified message model. When sending responses, the plugins construct platform-specific card layouts or text messages based on the unified outgoing message type, ensuring the AI core remains agnostic to presentation layer differences.

### Can the Channels subsystem support additional IM platforms beyond Telegram, Lark, and DingTalk?

Yes, the architecture supports arbitrary platform extensions through the **BasePlugin** abstraction. Developers create a new plugin class extending `BasePlugin` in `src/channels/plugins/`, implement the required lifecycle methods (`onInitialize`, `onStart`, `onStop`), and provide an adapter for message conversion. After registering the plugin via `ChannelManager.registerPlugin()`, the subsystem automatically includes it in the startup sequence and message routing pipeline without modifications to existing code.

### How does the subsystem manage connection failures or runtime errors?

The **PluginManager** tracks runtime errors for each plugin instance and exposes status events through the **BasePlugin** state machine. If `onStart` fails to establish a connection (e.g., invalid Telegram token or Lark WebSocket timeout), the plugin enters an error state and logs the failure. The singleton **ChannelManager** can query these statuses via `getAllPlugins` to provide health metrics, while individual plugins implement retry logic or graceful degradation within their connection handlers (such as DingTalk's REST API fallback when streaming fails).