SDK плагинов Channel
SDK плагинов Channel (@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.
* Когда сообщение поступает из этого Channel, возвращаемый текст
* будет добавлен к базовому системному промпту агента.
*/
getSystemPrompt?(
context: SystemPromptContext,
): string | undefined;
}
ChannelPluginFactory
Экспорт плагина по умолчанию должен быть фабричной функцией:
type ChannelPluginFactory = (context: ChannelPluginContext) => ChannelPlugin;
Elftia вызывает эту функцию при создании экземпляра Channel, передавая ChannelPluginContext. Плагин взаимодействует с основной системой через Context.
ChannelPluginContext
Context — единственный мост коммуникации между плагином и ядром Elftia:
interface ChannelPluginContext {
/** ID экземпляра Channel */
readonly channelId: string;
/** Отображаемое имя Channel */
readonly displayName: string;
// ─── Репортинг сообщений ──────────────────────
/** Сообщить о полученном входящем сообщении */
emitMessage(msg: InboundMessage): void;
/** Сообщить об изменении статуса соединения */
emitStatusChange(status: ChannelStatus): void;
/** Сообщить об ошибке */
emitError(error: Error): void;
/** Отправить пользовательское событие на фронтенд (например, QR-код для сопряжения) */
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": "Get from Discord Developer Portal",
"helpUrl": "https://discord.com/developers/applications"
}
],
"options": [
{
"key": "autoReconnect",
"label": "Auto Reconnect",
"type": "toggle",
"default": true,
"helpText": "Automatically try to reconnect when connection is lost"
}
],
"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 — плагин может только активно отправлять сообщения и не может получать входящие сообщения.
Следующие шаги
- Маршрутизация сообщений и конвейер безопасности — Полный поток обработки от плагина до агента
- Написание плагина Channel — Реализация плагина Channel с нуля