Channel 插件 SDK
Channel 插件 SDK(@elftia/channel-sdk)定义了所有 Channel 插件必须遵守的接口和类型。源码位于 packages/channel-sdk/src/。
ChannelPlugin 接口
每个 Channel 插件必须实现 ChannelPlugin 接口:
interface ChannelPlugin {
/** 唯一的 Channel 类型标识符,如 'discord', 'telegram' */
readonly type: string;
// ─── 生命周期(必须实现)─────────────────
/** 使用解密后的凭证建立连接 */
connect(
credentials: Record<string, string>,
options?: Record<string, unknown>,
): Promise<void>;
/** 断开连接并释放资源 */
disconnect(): Promise<void>;
/** 是否当前已连接 */
isConnected(): boolean;
// ─── 消息发送(必须实现)─────────────────
/** 向指定聊天发送文本消息 */
sendMessage(
chatId: string,
text: string,
options?: SendOptions,
): Promise<void>;
// ─── 可选能力 ────────────────────────────
/** 发送打字指示器 */
sendTyping?(chatId: string): Promise<void>;
/** 获取可用的聊天/频道列表 */
getChats?(): Promise<ChatInfo[]>;
/** 发送文件附件 */
sendAttachment?(
chatId: string,
attachment: AttachmentInput,
): Promise<void>;
/** 验证凭证有效性(不建立持久连接) */
validateCredentials?(
credentials: Record<string, string>,
): Promise<{ valid: boolean; error?: string }>;
/** 销毁插件实例,释放所有资源 */
dispose?(): Promise<void>;
// ─── AI 上下文注入 ────────────────────────
/**
* 返回 Channel 专属的 system prompt 片段。
* 当消息来自此 Channel 时,返回的文本会追加到 Agent 的基础 system prompt 中。
*/
getSystemPrompt?(
context: SystemPromptContext,
): string | undefined;
}
ChannelPluginFactory
插件的默认导出 (default export) 必须是一个工厂函数:
type ChannelPluginFactory = (context: ChannelPluginContext) => ChannelPlugin;
Elftia 在创建 Channel 实例时调用此函数,传入 ChannelPluginContext。插件通过 Context 与核心系统通信。
ChannelPluginContext
Context 是插件与 Elftia 核心之间的唯一通信桥梁:
interface ChannelPluginContext {
/** Channel 实例 ID */
readonly channelId: string;
/** Channel 显示名称 */
readonly displayName: string;
// ─── 消息上报 ────────────────────────────
/** 上报收到的入站消息 */
emitMessage(msg: InboundMessage): void;
/** 上报连接状态变化 */
emitStatusChange(status: ChannelStatus): void;
/** 上报错误 */
emitError(error: Error): void;
/** 发送自定义事件到前端(如二维码配对) */
emitEvent(eventType: string, data: unknown): void;
// ─── 日志 ─────────────────────────────────
/** 结构化日志 */
log: PluginLogger;
// ─── 存储 ─────────────────────────────────
/** 插件级别的 K-V 持久化存储 */
storage: PluginStorage;
// ─── 文件系统 ──────────────────────────────
/**
* 插件专属数据目录(跨会话持久化)。
* 框架自动创建。插件可用于下载文件、缓存、临时文件等。
* 路径示例:{userData}/channel-data/{channelId}/
*/
readonly dataDir: string;
}
PluginLogger
interface PluginLogger {
info(msg: string, meta?: Record<string, unknown>): void;
warn(msg: string, meta?: Record<string, unknown>): void;
error(msg: string, error?: Error): void;
debug(msg: string, meta?: Record<string, unknown>): void;
}
日志输出会自动添加 [Channel:{channelId}] 前缀,与 Elftia 的主日志系统集成。
PluginStorage
interface PluginStorage {
get<T = unknown>(key: string): Promise<T | null>;
set(key: string, value: unknown): Promise<void>;
delete(key: string): Promise<void>;
}
底层使用 SQLite 数据库持久化,上层有内存缓存加速读取。值以 JSON 序列化存储。
InboundMessage
插件通过 context.emitMessage() 上报的入站消息格式:
interface InboundMessage {
/** 消息唯一 ID(平台原始 ID) */
id: string;
/** 聊天/频道 ID */
chatId: string;
/** 发送者平台 ID */
senderId: string;
/** 发送者显示名称 */
senderName: string;
/** 消息文本内容 */
content: string;
/** ISO 8601 时间戳 */
timestamp: string;
/** 是否是机器人自身的消息 */
isFromMe: boolean;
/** 是否是群组消息 */
isGroup: boolean;
/** 回复的消息 ID */
replyToId?: string;
/** 附件列表 */
attachments?: ChannelAttachment[];
/** 平台特定的元数据 */
metadata?: Record<string, unknown>;
}
SendOptions
发送消息时的可选参数:
interface SendOptions {
/** 回复指定消息 */
replyToId?: string;
/** 消息格式 */
format?: 'text' | 'markdown' | 'html';
/** 论坛帖子/线程 ID */
threadId?: string;
/** 静默发送(不推送通知) */
silent?: boolean;
/** 媒体附件的说明文字 */
caption?: string;
/** 平台特定的回复标记(内联键盘等) */
replyMarkup?: unknown;
}
AttachmentInput
发送附件的输入格式:
interface AttachmentInput {
type: 'image' | 'file' | 'audio' | 'video';
/** 附件内容:Buffer 或 URL 字符串 */
data: Buffer | string;
/** 文件名 */
name: string;
/** MIME 类型 */
mimeType?: string;
}
SystemPromptContext
传递给 getSystemPrompt() 的上下文:
interface SystemPromptContext {
/** 聊天类型(如 'group', 'dm') */
chatType: string;
/** 聊天 ID */
chatId: string;
/** 发送者 ID */
senderId: string;
/** 发送者名称 */
senderName: string;
/** 是否群组 */
isGroup: boolean;
/** 平台特定元数据 */
metadata?: Record<string, unknown>;
}
清单文件 (elftia-channel.json)
每个插件必须在根目录包含 elftia-channel.json 清单文件:
{
"name": "@elftia/channel-discord",
"type": "discord",
"displayName": "Discord",
"version": "1.0.0",
"description": "Discord bot integration for Elftia",
"author": "Elftia Team",
"icon": "icon.svg",
"entry": "dist/index.cjs",
"credentials": [
{
"key": "botToken",
"label": "Bot Token",
"type": "password",
"required": true,
"placeholder": "MTA...",
"helpText": "从 Discord Developer Portal 获取",
"helpUrl": "https://discord.com/developers/applications"
}
],
"options": [
{
"key": "autoReconnect",
"label": "自动重连",
"type": "toggle",
"default": true,
"helpText": "连接断开时自动尝试重连"
}
],
"capabilities": {
"typing": true,
"reactions": true,
"attachments": true,
"threads": true,
"groupChat": true,
"sendOnly": false
},
"maxMessageLength": 2000,
"platformUrl": "https://discord.com",
"minElftiaVersion": "0.5.0"
}
清单字段说明
| 字段 | 类型 | 必填 | 说明 |
|---|---|---|---|
name | string | 是 | npm 包名(用于标识和去重) |
type | string | 是 | Channel 类型标识符(如 discord) |
displayName | string | 是 | 人类可读的平台名称 |
version | string | 是 | 语义化版本号 |
description | string | 否 | 插件描述 |
author | string | 否 | 作者名称 |
icon | string | 否 | 图标路径(相对于插件目录)或 SVG 字符串 |
entry | string | 是 | 入口文件路径(相对于插件目录) |
credentials | CredentialField[] | 是 | 凭证字段定义(用于 UI 表单渲染) |
options | OptionField[] | 否 | 非凭证配置项(如开关选项) |
capabilities | ChannelCapabilities | 否 | 平台能力声明 |
maxMessageLength | number | 否 | 平台消息长度上限 |
platformUrl | string | 否 | 平台官方网站 |
minElftiaVersion | string | 否 | 最低要求的 Elftia 版本 |
CredentialField
interface CredentialField {
key: string; // 字段键名
label: string; // 显示标签
type: 'text' | 'password' | 'textarea';
required: boolean;
placeholder?: string;
helpText?: string; // 输入提示
helpUrl?: string; // 帮助链接
}
OptionField
interface OptionField {
key: string;
label: string;
type: 'toggle'; // 目前仅支持开关类型
default?: boolean;
helpText?: string;
}
ChannelCapabilities
interface ChannelCapabilities {
typing?: boolean; // 打字指示器
reactions?: boolean; // 表情反应
attachments?: boolean; // 文件附件
threads?: boolean; // 帖子/线程
groupChat?: boolean; // 群组聊天
sendOnly?: boolean; // 仅发送(无入站消息)
}
sendOnly 用于桌面应用无法托管 Webhook 的场景——插件只能主动发送消息,不能接收入站消息。
下一步
- 消息路由与安全管线 — 消息从插件到 Agent 的完整处理流程
- 编写 Channel 插件 — 从零实现一个 Channel 插件