Перейти к основному содержимому

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"
}

Поля манифеста

ПолеТипОбязательноОписание
namestringДаИмя npm-пакета (для идентификации и дедупликации)
typestringДаИдентификатор типа Channel (например, discord)
displayNamestringДаЧеловекочитаемое название платформы
versionstringДаСемантическая версия
descriptionstringНетОписание плагина
authorstringНетИмя автора
iconstringНетПуть к иконке (относительно каталога плагина) или SVG-строка
entrystringДаПуть к входному файлу (относительно каталога плагина)
credentialsCredentialField[]ДаОпределения полей учётных данных (для рендеринга UI-формы)
optionsOptionField[]НетПараметры конфигурации, не являющиеся учётными данными (например, переключатели)
capabilitiesChannelCapabilitiesНетОбъявления возможностей платформы
maxMessageLengthnumberНетОграничение на длину сообщения на платформе
platformUrlstringНетОфициальный сайт платформы
minElftiaVersionstringНетМинимальная требуемая версия 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 — плагин может только активно отправлять сообщения и не может получать входящие сообщения.

Следующие шаги