Gateway
Route messages from messaging platforms to AI agents with sessions, middleware, and streaming.
Overview
The Gateway is the central class of @cogitator-ai/channels. It connects messaging channels (Telegram, Discord, Slack, WhatsApp, WebChat) to your AI agents, handling sessions, middleware, streaming, and message formatting.
pnpm add @cogitator-ai/channelsQuick Start
import { Cogitator, Agent } from '@cogitator-ai/core';
import { Gateway, telegramChannel } from '@cogitator-ai/channels';
const agent = new Agent({
name: 'assistant',
model: 'anthropic/claude-sonnet-4-20250514',
instructions: 'You are a helpful personal assistant.',
});
const gateway = new Gateway({
agent,
cogitator: new Cogitator({
llm: {
providers: { anthropic: { apiKey: process.env.ANTHROPIC_API_KEY } },
},
}),
channels: [
telegramChannel({ token: process.env.TG_TOKEN! }),
],
});
await gateway.start();GatewayConfig
interface GatewayConfig {
agent: Agent | ((user: ChannelUser) => Agent | Promise<Agent>);
channels: Channel[];
cogitator: Cogitator;
memory?: MemoryAdapter;
middleware?: GatewayMiddleware[];
session?: {
threadKey?: (msg: ChannelMessage) => string;
compaction?: CompactionConfig;
};
stream?: StreamConfig;
reactions?: StatusReactionConfig;
debounce?: DebounceConfig;
envelope?: EnvelopeConfig;
queueMode?: 'parallel' | 'sequential' | 'interrupt' | 'collect';
onError?: (error: Error, msg: ChannelMessage) => void;
}Per-user Agents
Pass a factory function to create different agents per user:
const gateway = new Gateway({
agent: (user) => {
if (user.id === 'admin') {
return adminAgent;
}
return defaultAgent;
},
// ...
});Message Flow
User sends message
→ Channel converts to ChannelMessage
→ Debouncer (if enabled): buffer rapid messages, merge into one
→ MessageQueue (if configured): sequential/interrupt/collect
→ Middleware pipeline (pairing → rate-limit → ...)
→ Envelope formatting (if enabled): wrap with [channel user +elapsed timestamp]
→ Session lookup/create
→ Status reaction: 👀 → 🤔 (if enabled)
→ cogitator.run(agent, { input, threadId, stream })
→ Status reaction: 👍 or 😱
→ StreamBuffer flushes progressively via editText
→ Platform-specific markdown adaptation
→ Response deliveredStatus Reactions
Show processing progress with emoji reactions on the user's message. Users see immediate feedback instead of waiting 10-30 seconds in silence.
const gateway = new Gateway({
// ...
reactions: {
enabled: true,
emojis: {
queued: '👀', // message received
thinking: '🤔', // LLM is generating
tool: '🔥', // tool execution
done: '👍', // response sent
error: '😱', // something failed
},
debounceMs: 700, // skip rapid phase changes
stallSoftMs: 10000, // show 🥱 after 10s on same phase
stallHardMs: 30000, // show 😨 after 30s
},
});Supported channels: Telegram (setMessageReaction), Discord (react). Channels without setReaction silently skip reactions.
Inbound Debouncing
When a user sends 3 messages in 1 second, without debouncing you get 3 parallel LLM calls. With debouncing, they merge into one.
const gateway = new Gateway({
// ...
debounce: {
enabled: true,
delayMs: 1500, // wait 1.5s of silence before processing
byChannel: { discord: 2000 }, // per-channel override
},
});Messages are grouped by channelType:channelId:userId. Texts are joined with newlines, attachments are merged.
Envelope Formatting
Give the LLM context about who is talking, from where, and when — without polluting agent instructions.
const gateway = new Gateway({
// ...
envelope: {
enabled: true,
includeTimestamp: true,
includeElapsed: true,
timezone: 'utc', // 'utc' | 'local' | IANA timezone string
},
});The user's message Hello world becomes:
[telegram Alice +2m30s Feb 28, 20:15] Hello worldThe elapsed time shows how long since the last message in this thread. The LLM naturally picks up on timing and context without explicit instructions.
Queue Modes
Control how concurrent messages from the same user/thread are handled.
const gateway = new Gateway({
// ...
queueMode: 'sequential',
});| Mode | Behavior |
|---|---|
parallel | Default. All messages processed immediately. |
sequential | FIFO per thread. Next message waits for current to finish. |
interrupt | Abort current processing, start new message immediately. |
collect | Buffer messages during processing, merge all when idle. |
parallel is the default and matches the behavior before Phase 4. For personal assistants, sequential or collect usually give the best UX.
Multi-channel Setup
import {
Gateway,
telegramChannel,
discordChannel,
slackChannel,
webchatChannel,
} from '@cogitator-ai/channels';
const gateway = new Gateway({
agent,
cogitator,
channels: [
telegramChannel({ token: process.env.TG_TOKEN! }),
discordChannel({ token: process.env.DISCORD_TOKEN! }),
slackChannel({
token: process.env.SLACK_BOT_TOKEN!,
signingSecret: process.env.SLACK_SIGNING_SECRET!,
appToken: process.env.SLACK_APP_TOKEN!,
}),
webchatChannel({ port: 18789 }),
],
});Platform Comparison
| Feature | Telegram | Discord | Slack | WebChat | |
|---|---|---|---|---|---|
| Streaming (edit) | ✅ | ✅ | ✅ | ❌ | ✅ |
| Reactions | ✅ | ✅ | ❌ | ❌ | ❌ |
| Typing indicator | ✅ | ✅ | ⚠️ | ✅ | ✅ |
| Media (vision) | ✅ | ❌ | ❌ | ❌ | ❌ |
| Voice (STT) | ✅ | ❌ | ❌ | ❌ | ❌ |
| Max message length | 4096 | 2000 | ~4000 | ~65536 | unlimited |
| Public URL needed | webhook only | no | HTTP mode only | no | no |
| Auth model | Bot token | Bot token | 3 tokens | QR code | custom |
Session Management
Gateway integrates with @cogitator-ai/memory for persistent conversations:
import { SqliteMemory } from '@cogitator-ai/memory';
const gateway = new Gateway({
// ...
memory: new SqliteMemory({ path: './sessions.db' }),
session: {
threadKey: (msg) => `${msg.channelType}:${msg.userId}`,
compaction: {
strategy: 'summary',
threshold: 100,
keepRecent: 20,
},
},
});The threadKey function determines how sessions are grouped. By default, each user gets their own session per channel.
Stats
const stats = gateway.stats;
// {
// uptime: 7200000,
// activeSessions: 3,
// totalSessions: 12,
// messagesToday: 847,
// connectedChannels: ['telegram', 'discord'],
// }Environment Variables
A typical .env for a multi-channel bot:
# LLM
GOOGLE_API_KEY=...
# Telegram
TG_TOKEN=7204891735:AAHr...
# Discord
DISCORD_TOKEN=MTI...
# Slack
SLACK_BOT_TOKEN=xoxb-...
SLACK_SIGNING_SECRET=...
SLACK_APP_TOKEN=xapp-...
# WebChat
WEBCHAT_SECRET=your-secret-tokenGraceful Shutdown
process.on('SIGTERM', async () => {
await gateway.stop(); // flushes debouncer, disposes queue, stops channels
process.exit(0);
});