channel / reference
createChannels
Boots the multichannel runtime: every configured platform feeds the same inbound pipeline (canonical
channel_messages on InstantDB → agent reaction → reply through the platform), and exposes one webhook handler per platform. Delivery internals are an implementation detail of the package.Signature
export async function createChannels(
options: CreateChannelsOptions,
): Promise<ChannelsRuntime>Call it once at boot (a module-level await in a server file is fine). It lazily loads the platform runtime, connects every configured platform to the same store and reaction pipeline, and returns a ChannelsRuntime with one webhook handler per platform.
CreateChannelsOptions
| prop | type | description |
|---|---|---|
| db * | any | InstantDB admin client with the channel domain schema pushed. The runtime persists canonical messages and its internal delivery state (channel_state, channel_locks, ...) through this client. |
| userName * | string | Bot identity across platforms — the name the agent posts under. |
| platforms * | ChannelPlatformsConfig | One entry per channel to enable. The shape of each entry is that platform's credentials/config (bot tokens, signing secrets); it is passed through to the underlying delivery runtime. |
| resolveContextId * | (params: { channel: ChannelKind; threadKey: string }) => Promise<string> | Maps a platform conversation to an agent context. Called with the channel kind and the stable per-platform threadKey; return the contextId the conversation belongs to (typically by ensuring an agent thread keyed on `${channel}:${threadKey}`). |
| react * | (inbound: ChannelInbound) => Promise<string | null | undefined | void> | Reacts to an inbound message (typically: thread.react on the agent domain). Return text to auto-reply, or use inbound.reply for streaming / multi-part replies and return null. |
ChannelPlatformsConfig
export type ChannelPlatformsConfig = Partial<
Record<"slack" | "teams" | "gchat" | "discord" | "telegram", Record<string, unknown>>
> &
Record<string, Record<string, unknown> | undefined>;The five known platform keys get autocompletion; the index signature keeps the map open for additional platforms. Each value is an opaque Record<string, unknown> of platform credentials, forwarded verbatim to the delivery runtime.
ChannelInbound
The object handed to your react callback for every inbound message.
| prop | type | description |
|---|---|---|
| channel * | ChannelKind | Which channel the message arrived on ("slack", "telegram", ...). Open union — custom kinds are allowed. |
| threadKey * | string | Stable per-platform conversation key. Use it to key agent threads so the same platform conversation always maps to the same thread. |
| contextId * | string | The agent context this conversation maps to — the value your resolveContextId returned. |
| message * | ChannelMessage | The persisted canonical inbound message. Already written to channel_messages and linked to the context before react runs. |
| reply * | (text: string) => Promise<void> | Posts a reply on the same platform thread and persists it as an outbound channel_messages record. Call it any number of times for multi-part replies. |
ChannelsRuntime
| prop | type | description |
|---|---|---|
| platforms * | string[] | Platforms that were enabled, in the order they were configured. |
| webhooks * | Record<string, (request: Request, options?: Record<string, unknown>) => Promise<Response>> | Webhook handlers to mount per platform — one fetch-style handler keyed by platform name (e.g. mount under app/api/channels/[platform]). |
| store * | ChannelMessageStore | Canonical message store, for app-side writes (broadcasts, outbound messages you trigger yourself, etc). |
| dispose * | () => Promise<void> | Tears the runtime down — disconnects platforms and releases internal resources. |
Complete example
import { createChannels } from "@ekairos/channel/platforms";
import { db } from "@/lib/db"; // InstantDB admin client
import { ensureThread, reactOnThread } from "@/lib/agent";
export const channels = await createChannels({
db,
userName: "ekairos",
platforms: {
slack: {
botToken: process.env.SLACK_BOT_TOKEN!,
signingSecret: process.env.SLACK_SIGNING_SECRET!,
},
telegram: { botToken: process.env.TELEGRAM_BOT_TOKEN! },
},
resolveContextId: async ({ channel, threadKey }) => {
const thread = await ensureThread({ key: `${channel}:${threadKey}` });
return thread.contextId;
},
react: async (inbound) => {
const reaction = await reactOnThread(inbound.contextId, inbound.message);
return reaction.text;
},
});import { channels } from "@/lib/channels";
export async function POST(
request: Request,
{ params }: { params: Promise<{ platform: string }> },
) {
const { platform } = await params;
const handler = channels.webhooks[platform];
if (!handler) {
return new Response("unknown platform", { status: 404 });
}
return handler(request);
}