Build a custom adapter
Implement Channel<RawEvent> and keep every platform SDK type inside the adapter package. See Channel core for the shared types, and Capabilities and rate limits for the optional operations.
Required members
Five members are required:
platformnames the adapter and must matchChannelAddress.platform.splitMessage(message)bounds output for the platform.start(handler)connects and begins delivering normalized events throughhandler.stop()detaches listeners and drains in-flight deliveries.send(address, message)delivers one already-bounded message.
capabilities, loadAttachment(event, attachment, signal?), edit(sent, message), delete(sent), showTyping(address), react(sent, reaction), and unreact(sent, reaction) are optional.
import { splitChannelMessage } from '@anvia/channel'
import type {
Channel,
ChannelAddress,
ChannelEventHandler,
ChannelMessage,
SentChannelMessage,
} from '@anvia/channel'
type AcmeEvent = Readonly<{ id: string; body: unknown }>
export class AcmeChannel implements Channel<AcmeEvent> {
readonly platform = 'acme'
readonly capabilities = { actions: false } as const
splitMessage(message: ChannelMessage): readonly ChannelMessage[] {
return splitChannelMessage({ message, maximumLength: 2_000 })
}
async start(handler: ChannelEventHandler<AcmeEvent>): Promise<void> {
// Connect the Acme SDK, validate its payloads, normalize them, then await handler(event).
}
async stop(): Promise<void> {
// Detach listeners, close the SDK client, and drain in-flight deliveries.
}
async send(address: ChannelAddress, message: ChannelMessage): Promise<SentChannelMessage> {
// Validate the address and message before calling the platform API.
return { id: 'platform-message-id', address }
}
}Implement loadAttachment() when normalized incoming messages expose attachment metadata. It receives the original message event plus the attachment metadata and an optional AbortSignal, and resolves to { type: 'url' } or { type: 'data' } bytes. Never put authenticated download URLs or platform credentials into a normalized event.
splitMessage() must return at least one message — returning an empty array makes sendChannelMessage() throw instead of delivering nothing.
edit() is optional: omit it for text-only adapters and gate calls on capabilities.messageEdits === true && channel.edit !== undefined. When edit is omitted, a channel-agent service skips live editing and delivers the completed response through send instead. See Channel agent for the bridge that connects an adapter to an agent.