lobehub/docs/development/basic/add-new-bot-platform.mdx
Rdmclin2 475622a4b9
feat: support multi media and multiple connection mode (#13624)
* test: add feishu and qq test cases

* feat: support qq websocket mode

* feat: support slack websocket mode

* feat: feishu/lark support websocket connectMode

* chore: add default connection mode

* fix: discord 401 sign error

* fix: feishu websocket need verification token

* fix: heartbeate interval

* fix: get effective connnection mode

* chore: extract  getEffectiveConnectionMode utils

* chore: merge with default settings

* chore: add connectionMode fallback

* fix: file extract error

* fix: list platforms by connectionMode

* fix: qq bot gateway

* fix: support fileList

* feat: support video list

* chore: migrate local testing to references

* chore: add bot skill

* fix: telegram file serialize error

* feat: extract file extract logic to platform client

* fix: wechat file read

* feat: skip no metion in thread and set default message mode to queue

* chore: refact download resources

* fix: feishu adapter mention and dm error

* fix: feishu thread id

* fix: slack remove action

* fix: bot resovle files
2026-04-09 14:16:03 +08:00

429 lines
15 KiB
Text

---
title: Adding a New Bot Platform
description: >-
Learn how to add a new bot platform (e.g., Slack, WhatsApp) to LobeHub's
channel system, including schema definition, client implementation, and
platform registration.
tags:
- Bot Platform
- Message Channels
- Integration
- Development Guide
---
# Adding a New Bot Platform
This guide walks through the steps to add a new bot platform to LobeHub's channel system. The platform architecture is modular — each platform is a self-contained directory under `src/server/services/bot/platforms/`.
## Architecture Overview
```
src/server/services/bot/platforms/
├── types.ts # Core interfaces (FieldSchema, PlatformClient, ClientFactory, etc.)
├── registry.ts # PlatformRegistry class
├── index.ts # Singleton registry + platform registration
├── utils.ts # Shared utilities
├── discord/ # Example: Discord platform
│ ├── definition.ts # PlatformDefinition export
│ ├── schema.ts # FieldSchema[] for credentials & settings
│ ├── client.ts # ClientFactory + PlatformClient implementation
│ └── api.ts # Platform API helper class
└── <your-platform>/ # Your new platform
```
**Key concepts:**
- **FieldSchema** — Declarative schema that drives both server-side validation and frontend form auto-generation
- **PlatformClient** — Runtime interface for interacting with the platform (messaging, lifecycle)
- **ClientFactory** — Creates PlatformClient instances and validates credentials
- **PlatformDefinition** — Metadata + schema + factory, registered in the global registry
- **Chat SDK Adapter** — Bridges the platform's webhook/events into the unified Chat SDK
## Prerequisite: Chat SDK Adapter
Each platform requires a **Chat SDK adapter** that bridges the platform's webhook events into the unified [Vercel Chat SDK](https://github.com/vercel/chat) (`chat` npm package). Before implementing the platform, determine which adapter to use:
### Option A: Use an existing npm adapter
Some platforms have official adapters published under `@chat-adapter/*`:
- `@chat-adapter/discord` — Discord
- `@chat-adapter/slack` — Slack
- `@chat-adapter/telegram` — Telegram
Check npm with `npm view @chat-adapter/<platform>` to see if one exists.
### Option B: Develop a custom adapter in `packages/`
If no npm adapter exists, you need to create one as a workspace package. Reference the existing implementations:
- `packages/chat-adapter-feishu` — Feishu/Lark adapter (`@lobechat/chat-adapter-feishu`)
- `packages/chat-adapter-qq` — QQ adapter (`@lobechat/chat-adapter-qq`)
Each adapter package follows this structure:
```
packages/chat-adapter-<platform>/
├── package.json # name: @lobechat/chat-adapter-<platform>
├── tsconfig.json
├── tsup.config.ts
└── src/
├── index.ts # Public exports: createXxxAdapter, XxxApiClient, etc.
├── adapter.ts # Adapter class implementing chat SDK's Adapter interface
├── api.ts # Platform API client (webhook verification, message parsing)
├── crypto.ts # Request signature verification
├── format-converter.ts # Message format conversion (platform format ↔ chat SDK AST)
└── types.ts # Platform-specific type definitions
```
Key points for developing a custom adapter:
- The adapter must implement the `Adapter` interface from the `chat` package
- It handles webhook request verification, event parsing, and message format conversion
- The `createXxxAdapter(config)` factory function is what `PlatformClient.createAdapter()` will call
- Add `"chat": "^4.14.0"` as a dependency in `package.json`
## Step 1: Create the Platform Directory
```bash
mkdir src/server/services/bot/platforms/<platform-name>
```
You will create four files:
| File | Purpose |
| --------------- | ------------------------------------------------- |
| `schema.ts` | Credential and settings field definitions |
| `api.ts` | Lightweight API client for outbound messaging |
| `client.ts` | `ClientFactory` + `PlatformClient` implementation |
| `definition.ts` | `PlatformDefinition` export |
## Step 2: Define the Schema (`schema.ts`)
The schema is an array of `FieldSchema` objects with two top-level sections: `credentials` and `settings`.
```ts
import type { FieldSchema } from '../types';
export const schema: FieldSchema[] = [
{
key: 'credentials',
label: 'channel.credentials',
properties: [
{
key: 'applicationId',
description: 'channel.applicationIdHint',
label: 'channel.applicationId',
required: true,
type: 'string',
},
{
key: 'botToken',
description: 'channel.botTokenEncryptedHint',
label: 'channel.botToken',
required: true,
type: 'password', // Encrypted in storage, masked in UI
},
],
type: 'object',
},
{
key: 'settings',
label: 'channel.settings',
properties: [
{
key: 'charLimit',
default: 4000,
description: 'channel.charLimitHint',
label: 'channel.charLimit',
minimum: 100,
type: 'number',
},
// Add platform-specific settings...
],
type: 'object',
},
];
```
**Schema conventions:**
- `type: 'password'` fields are encrypted at rest and masked in the form
- Use existing i18n keys (e.g., `channel.botToken`, `channel.charLimit`) for shared fields
- Use `channel.<platform>.<key>` for platform-specific i18n keys
- `devOnly: true` fields only appear when `NODE_ENV === 'development'`
- Credentials must include a field that resolves to `applicationId` — either an explicit `applicationId` field, an `appId` field, or a `botToken` from which the ID is derived (see `resolveApplicationId` in the channel detail page)
## Step 3: Create the API Client (`api.ts`)
A lightweight class for outbound messaging operations used by the callback service (outside the Chat SDK adapter):
```ts
import debug from 'debug';
const log = debug('bot-platform:<platform>:client');
export const API_BASE = 'https://api.example.com';
export class PlatformApi {
private readonly token: string;
constructor(token: string) {
this.token = token;
}
async sendMessage(channelId: string, text: string): Promise<{ id: string }> {
log('sendMessage: channel=%s', channelId);
return this.call('messages.send', { channel: channelId, text });
}
async editMessage(channelId: string, messageId: string, text: string): Promise<void> {
log('editMessage: channel=%s, message=%s', channelId, messageId);
await this.call('messages.update', { channel: channelId, id: messageId, text });
}
// ... other operations (typing indicator, reactions, etc.)
private async call(method: string, body: Record<string, unknown>): Promise<any> {
const response = await fetch(`${API_BASE}/${method}`, {
body: JSON.stringify(body),
headers: {
Authorization: `Bearer ${this.token}`,
'Content-Type': 'application/json',
},
method: 'POST',
});
if (!response.ok) {
const text = await response.text();
log('API error: method=%s, status=%d, body=%s', method, response.status, text);
throw new Error(`API ${method} failed: ${response.status} ${text}`);
}
return response.json();
}
}
```
## Step 4: Implement the Client (`client.ts`)
Implement `PlatformClient` and extend `ClientFactory`:
```ts
import { createPlatformAdapter } from '@chat-adapter/<platform>';
import debug from 'debug';
import {
type BotPlatformRuntimeContext,
type BotProviderConfig,
ClientFactory,
type PlatformClient,
type PlatformMessenger,
type ValidationResult,
} from '../types';
import { PlatformApi } from './api';
const log = debug('bot-platform:<platform>:bot');
class MyPlatformClient implements PlatformClient {
readonly id = '<platform>';
readonly applicationId: string;
private config: BotProviderConfig;
private context: BotPlatformRuntimeContext;
constructor(config: BotProviderConfig, context: BotPlatformRuntimeContext) {
this.config = config;
this.context = context;
this.applicationId = config.applicationId;
}
// --- Lifecycle ---
async start(): Promise<void> {
// Register webhook or start listening
// For webhook platforms: configure the webhook URL with the platform API
// For gateway platforms: open a persistent connection
}
async stop(): Promise<void> {
// Cleanup: remove webhook registration or close connection
}
// --- Runtime Operations ---
createAdapter(): Record<string, any> {
// Return a Chat SDK adapter instance for inbound message handling
return {
'<platform>': createPlatformAdapter({
botToken: this.config.credentials.botToken,
// ... adapter-specific config
}),
};
}
getMessenger(platformThreadId: string): PlatformMessenger {
const api = new PlatformApi(this.config.credentials.botToken);
const channelId = platformThreadId.split(':')[1];
return {
createMessage: (content) => api.sendMessage(channelId, content).then(() => {}),
editMessage: (messageId, content) => api.editMessage(channelId, messageId, content),
removeReaction: (messageId, emoji) => api.removeReaction(channelId, messageId, emoji),
triggerTyping: () => Promise.resolve(),
};
}
extractChatId(platformThreadId: string): string {
return platformThreadId.split(':')[1];
}
parseMessageId(compositeId: string): string {
return compositeId;
}
// --- Optional methods ---
// sanitizeUserInput(text: string): string { ... }
// shouldSubscribe(threadId: string): boolean { ... }
// formatReply(body: string, stats?: UsageStats): string { ... }
}
export class MyPlatformClientFactory extends ClientFactory {
createClient(config: BotProviderConfig, context: BotPlatformRuntimeContext): PlatformClient {
return new MyPlatformClient(config, context);
}
async validateCredentials(credentials: Record<string, string>): Promise<ValidationResult> {
// Call the platform API to verify the credentials are valid
try {
const res = await fetch('https://api.example.com/auth.test', {
headers: { Authorization: `Bearer ${credentials.botToken}` },
method: 'POST',
});
if (!res.ok) throw new Error(`HTTP ${res.status}`);
return { valid: true };
} catch {
return {
errors: [{ field: 'botToken', message: 'Failed to authenticate' }],
valid: false,
};
}
}
}
```
**Key interfaces to implement:**
| Method | Purpose |
| --------------------- | ----------------------------------------------------------- |
| `start()` | Register webhook or start gateway listener |
| `stop()` | Clean up resources on shutdown |
| `createAdapter()` | Return Chat SDK adapter for inbound event handling |
| `getMessenger()` | Return outbound messaging interface for a thread |
| `extractChatId()` | Parse platform channel ID from composite thread ID |
| `parseMessageId()` | Convert composite message ID to platform-native format |
| `sanitizeUserInput()` | *(Optional)* Strip bot mention artifacts from user input |
| `shouldSubscribe()` | *(Optional)* Control thread auto-subscription behavior |
| `formatReply()` | *(Optional)* Append platform-specific formatting to replies |
## Step 5: Export the Definition (`definition.ts`)
```ts
import type { PlatformDefinition } from '../types';
import { MyPlatformClientFactory } from './client';
import { schema } from './schema';
export const myPlatform: PlatformDefinition = {
id: '<platform>',
name: 'Platform Name',
connectionMode: 'webhook', // 'webhook' | 'websocket' | 'polling'
description: 'Connect a Platform bot',
documentation: {
portalUrl: 'https://developers.example.com',
setupGuideUrl: 'https://lobehub.com/docs/usage/channels/<platform>',
},
schema,
showWebhookUrl: true, // Set to true if users need to manually copy the webhook URL
clientFactory: new MyPlatformClientFactory(),
};
```
**`showWebhookUrl`:** Set to `true` for platforms where the user must manually paste a webhook URL (e.g., Slack, Feishu). Set to `false` (or omit) for platforms that auto-register webhooks via API (e.g., Telegram).
## Step 6: Register the Platform
Edit `src/server/services/bot/platforms/index.ts`:
```ts
import { myPlatform } from './<platform>/definition';
// Add to exports
export { myPlatform } from './<platform>/definition';
// Register
platformRegistry.register(myPlatform);
```
## Step 7: Add i18n Keys
### Default keys (`src/locales/default/agent.ts`)
Add platform-specific keys. Reuse generic keys where possible:
```ts
// Reusable (already exist):
// 'channel.botToken', 'channel.applicationId', 'channel.charLimit', etc.
// Platform-specific:
'channel.<platform>.description': 'Connect this assistant to Platform for ...',
'channel.<platform>.someFieldHint': 'Description of this field.',
```
### Translations (`locales/zh-CN/agent.json`, `locales/en-US/agent.json`)
Add corresponding translations for all new keys in both locale files.
## Step 8: Add User Documentation
Create setup guides in `docs/usage/channels/`:
- `<platform>.mdx` — English guide
- `<platform>.zh-CN.mdx` — Chinese guide
Follow the structure of existing docs (e.g., `discord.mdx`): Prerequisites → Create App → Configure in LobeHub → Configure Webhooks → Test Connection → Configuration Reference → Troubleshooting.
## Frontend: Automatic UI Generation
The frontend automatically generates the configuration form from the schema. No frontend code changes are needed unless your platform requires a custom icon. The icon resolution works by matching the platform `name` against known icons in `@lobehub/ui/icons`:
```
// src/routes/(main)/agent/channel/const.ts
const ICON_NAMES = ['Discord', 'GoogleChat', 'Lark', 'Slack', 'Telegram', ...];
```
If your platform's `name` matches an icon name (case-insensitive), the icon is used automatically. Otherwise, add an alias in `ICON_ALIASES`.
## Webhook URL Pattern
All platforms share the same webhook route:
```
POST /api/agent/webhooks/[platform]/[appId]
```
The `BotMessageRouter` handles routing, on-demand bot loading, and Chat SDK integration automatically.
## Checklist
- [ ] Ensure a Chat SDK adapter exists (`@chat-adapter/*` on npm or custom `packages/chat-adapter-<platform>`)
- [ ] Create `src/server/services/bot/platforms/<platform>/`
- [ ] `schema.ts` — Field definitions for credentials and settings
- [ ] `api.ts` — Outbound API client
- [ ] `client.ts` — `ClientFactory` + `PlatformClient`
- [ ] `definition.ts` — `PlatformDefinition` export
- [ ] Register in `src/server/services/bot/platforms/index.ts`
- [ ] Add i18n keys in `src/locales/default/agent.ts`
- [ ] Add translations in `locales/zh-CN/agent.json` and `locales/en-US/agent.json`
- [ ] Add setup docs in `docs/usage/channels/<platform>.mdx` (en + zh-CN)
- [ ] Verify icon resolves in `const.ts` (or add alias)