@cogitator-ai/channels
v0.2.1
Published
Messaging channel connectors and Gateway for Cogitator AI agents
Readme
@cogitator-ai/channels
Connect Cogitator agents to messaging platforms. Gateway routes messages across channels, handles sessions, streaming, media, and middleware.
Install
pnpm add @cogitator-ai/channels
# Then install the adapters you need:
pnpm add grammy # Telegram
pnpm add discord.js # Discord
pnpm add @slack/bolt # Slack
pnpm add @whiskeysockets/baileys # WhatsApp
pnpm add ws # WebChatQuick Start
import { Cogitator, Agent } from '@cogitator-ai/core';
import { Gateway, telegramChannel } from '@cogitator-ai/channels';
const cogitator = new Cogitator({ llm: { defaultModel: 'google/gemini-2.5-flash' } });
const agent = new Agent({ name: 'bot', instructions: 'You are a helpful assistant.' });
const gateway = new Gateway({
cogitator,
agent,
channels: [telegramChannel({ token: process.env.TG_TOKEN! })],
stream: { flushInterval: 500, minChunkSize: 20 },
});
await gateway.start();Channels
Telegram
Get your token: Open Telegram → search @BotFather → send /newbot → follow prompts → copy the token.
import { telegramChannel } from '@cogitator-ai/channels';
telegramChannel({ token: process.env.TG_TOKEN! });
// Webhook mode (production):
telegramChannel({
token: process.env.TG_TOKEN!,
webhook: { url: 'https://example.com/telegram', port: 8443 },
});Supports: text, photos (→ vision), voice (→ STT), documents, streaming via editText, emoji reactions, 4096 char limit.
Discord
Get your token: Discord Developer Portal → New Application → Bot → Reset Token → copy it. Enable "Message Content Intent" under Privileged Gateway Intents. Invite via OAuth2 URL Generator with bot scope + Send Messages, Read Message History, Add Reactions permissions.
import { discordChannel } from '@cogitator-ai/channels';
discordChannel({
token: process.env.DISCORD_TOKEN!,
mentionOnly: true, // only respond when @mentioned in servers
});Supports: DMs, server messages, streaming, reactions, auto-chunking (2000 char limit, code-block aware).
Slack
Get your tokens (3 total):
- api.slack.com/apps → Create New App → From scratch
- Socket Mode → enable → generate App Token (
xapp-...) → save asSLACK_APP_TOKEN - OAuth & Permissions → add scopes:
chat:write,app_mentions:read,im:history,im:read,im:write - Event Subscriptions → enable → add:
message.im,app_mention - Install App → copy Bot Token (
xoxb-...) → save asSLACK_BOT_TOKEN - Basic Information → copy Signing Secret → save as
SLACK_SIGNING_SECRET
import { slackChannel } from '@cogitator-ai/channels';
slackChannel({
token: process.env.SLACK_BOT_TOKEN!,
signingSecret: process.env.SLACK_SIGNING_SECRET!,
appToken: process.env.SLACK_APP_TOKEN!,
});Supports: DMs, channel messages (via @mention), Socket Mode (no public URL), streaming via chat.update.
No bot API — connects as a linked device to your WhatsApp account (like WhatsApp Web).
import { whatsappChannel } from '@cogitator-ai/channels';
whatsappChannel({
sessionPath: '.cogitator/whatsapp-session',
printQr: true,
});First run: QR code prints to terminal → scan from WhatsApp → Settings → Linked Devices. Session is saved for future restarts.
Supports: 1-on-1 and group chats, typing indicators, auto-reconnect, WhatsApp-native markdown. No streaming (WhatsApp doesn't support editing).
WebChat
WebSocket server for custom UIs, CLI tools, or any client.
import { webchatChannel } from '@cogitator-ai/channels';
webchatChannel({
port: 3100,
path: '/ws',
auth: (token) => token === process.env.WEBCHAT_SECRET,
});Connect: ws://localhost:3100/ws?token=YOUR_SECRET. Send {"text": "Hello!"}, receive {"type": "message", "id": "...", "text": "..."}.
Supports: streaming (via edit messages), typing indicators, token auth, no message limit.
Platform Comparison
| Feature | Telegram | Discord | Slack | WhatsApp | WebChat | | ------------------ | ------------ | ------- | --------- | -------- | --------- | | Streaming (edit) | ✅ | ✅ | ✅ | ❌ | ✅ | | Reactions | ✅ | ✅ | ❌ | ❌ | ❌ | | Typing indicator | ✅ | ✅ | ⚠️ | ✅ | ✅ | | Photos → vision | ✅ | ❌ | ❌ | ❌ | ❌ | | Voice → STT | ✅ | ❌ | ❌ | ❌ | ❌ | | Max message length | 4096 | 2000 | ~4000 | ~65536 | unlimited | | Public URL needed | webhook only | no | HTTP only | no | no |
Gateway
Routes incoming messages to your agent. Handles:
- Session management (per-user, per-channel)
- Streaming with
StreamBuffer(chunked message editing) - Typing keep-alive (re-sends typing indicator every 4s)
- Media processing (images → vision, voice → STT)
- Middleware pipeline
- Platform-specific markdown conversion
- Status reactions, debouncing, envelope formatting, queue modes
Status Reactions
Emoji progress indicators on the user's message:
const gateway = new Gateway({
// ...
reactions: {
enabled: true,
emojis: { queued: '👀', thinking: '🤔', tool: '🔥', done: '👍', error: '😱' },
debounceMs: 700,
stallSoftMs: 10000,
stallHardMs: 30000,
},
});Inbound Debouncing
Merge rapid messages into a single LLM call:
const gateway = new Gateway({
// ...
debounce: {
enabled: true,
delayMs: 1500,
byChannel: { discord: 2000 },
},
});Envelope Formatting
Wrap messages with context so the LLM knows who's talking, on which platform, and when:
[14:23 MSK | telegram | @alice | DM | +2m30s] Hello world
const gateway = new Gateway({
// ...
envelope: {
enabled: true,
includeTimestamp: true,
includeElapsed: true,
includeSender: true,
includeChannel: true,
includeChatType: true,
timezone: 'Europe/Moscow',
},
});Queue Modes
Control concurrent message handling per user:
const gateway = new Gateway({
// ...
queueMode: 'sequential', // 'parallel' | 'sequential' | 'interrupt' | 'collect'
});- parallel — default, all messages processed immediately
- sequential — FIFO per thread, wait for current to finish
- interrupt — abort current, start new message
- collect — buffer during processing, merge on idle
Formatting Pipeline
Platform-specific markdown conversion applied automatically:
- Telegram —
# headers→ bold, preserves code blocks - Discord — native Markdown, auto-chunks at 2000 chars with code block integrity
- Slack —
**bold**→*bold*, headers → bold - WhatsApp —
**bold**→*bold*,~~strike~~→~strike~
Middleware
import { rateLimit, ownerCommands, DmPolicyMiddleware, autoExtract } from '@cogitator-ai/channels';
const gateway = new Gateway({
// ...
middleware: [
rateLimit({ maxPerMinute: 30 }),
ownerCommands({
ownerIds: { telegram: '123' },
commandLevels: { status: 'authorized', block: 'owner', help: 'public' },
}),
new DmPolicyMiddleware({
mode: 'pairing',
ownerIds: { telegram: '123' },
storePath: '~/.cogitator/dm-allowlist.json',
groupPolicy: 'open',
}),
autoExtract({ extractor }),
],
});DM Policy Modes
- open — anyone can DM the bot
- allowlist — only pre-approved users
- pairing — unknown users get a code, owner approves via
/pair CODE - disabled — DMs blocked entirely
Command Authorization Levels
Commands have 3 access levels: owner, authorized, public. Owner-only commands (like /block, /model) can't be run by regular users even if they're approved.
Lifecycle Hooks
Subscribe to events across the message lifecycle:
import { createHookRegistry } from '@cogitator-ai/channels';
const hooks = createHookRegistry();
hooks.on('message:received', (e) => console.log('New message:', e));
hooks.on('agent:after_run', (e) => console.log('Response:', e));
hooks.on('agent:error', (e) => console.error('Agent failed:', e));
const gateway = new Gateway({ /* ... */ hooks });Available hooks: message:received, message:sending, message:sent, agent:before_run, agent:after_run, agent:error, session:created, session:compacted, stream:started, stream:finished.
Errors in one handler don't affect others.
Media & STT
Voice messages are transcribed automatically. STT provider is selected by available env vars (highest priority first):
- Deepgram — set
DEEPGRAM_API_KEY(nova-3 model, fast and accurate) - Groq — set
GROQ_API_KEY(free tier available) - OpenAI — set
OPENAI_API_KEY - Local Whisper — downloads ~75MB model on first use, runs offline (no API key needed)
Images are passed to the LLM as vision input if the model supports it.
Scheduler
import { HeartbeatScheduler, SimpleTimerStore } from '@cogitator-ai/channels';
const store = new SimpleTimerStore({ persistPath: '.cogitator/timers.json' });
const scheduler = new HeartbeatScheduler(store, {
onFire: (msg) => gateway.injectMessage(msg),
pollInterval: 30_000,
maxRetries: 5,
onRunComplete: (entry, status, error, durationMs) => {
console.log(`Job ${entry.id}: ${status} (${durationMs}ms)`);
},
});
scheduler.start();Schedule types: cron (recurring), recurring (interval), fixed (one-shot). Error tracking with auto-backoff.
await scheduler.listJobs();
await scheduler.enableJob(id);
await scheduler.disableJob(id);
await scheduler.cancelJob(id);Environment Variables
# LLM Provider
GOOGLE_API_KEY=... # or ANTHROPIC_API_KEY, OPENAI_API_KEY
# Telegram
TG_TOKEN=7204891735:AAHr...
# Discord
DISCORD_TOKEN=MTI...
# Slack (3 tokens)
SLACK_BOT_TOKEN=xoxb-...
SLACK_SIGNING_SECRET=...
SLACK_APP_TOKEN=xapp-...
# WhatsApp — no token, uses QR pairing
# WebChat
WEBCHAT_SECRET=your-secret
# STT (optional, for voice messages — pick one)
DEEPGRAM_API_KEY=... # highest priority
GROQ_API_KEY=... # free tier
OPENAI_API_KEY=... # fallback