본문으로 건너뛰기

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 전용 시스템 프롬프트 단편을 반환합니다.
* 이 Channel에서 메시지가 수신되면 반환된 텍스트가
* Agent의 기본 시스템 프롬프트에 추가됩니다.
*/
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;
/** 프론트엔드에 커스텀 이벤트 전송 (예: 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": "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"
}

매니페스트 필드

필드타입필수설명
namestringnpm 패키지 이름 (식별 및 중복 제거에 사용)
typestringChannel 타입 식별자 (예: 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을 호스팅할 수 없는 데스크탑 앱을 위한 것입니다. 플러그인이 능동적으로 메시지를 전송할 수만 있고 인바운드 메시지를 수신할 수 없는 경우에 사용합니다.

다음 단계