@hastenr/chatapi-sdk
v0.1.13
Published
Official ChatAPI TypeScript SDK for real-time messaging
Maintainers
Readme
@hastenr/chatapi-sdk
The official TypeScript SDK for ChatAPI — self-hosted, real-time messaging infrastructure for apps where AI is a participant.
Installation
npm install @hastenr/chatapi-sdkQuick Start
import { ChatAPI } from '@hastenr/chatapi-sdk';
const chat = new ChatAPI({
baseURL: 'http://localhost:8080',
token: '<your-jwt>', // signed by your backend with JWT_SECRET
displayName: 'Alice', // optional, attached to sent messages
});
// Connect WebSocket
await chat.connect();
// Listen for incoming messages
chat.on('message', (event) => {
console.log(`${event.sender_id}: ${event.content}`);
});
// Send a message
await chat.messages.send('room_123', 'Hello!');Configuration
| Option | Type | Required | Default | Description |
|---|---|---|---|---|
| baseURL | string | Yes | — | ChatAPI server base URL |
| token | string | Yes | — | JWT signed by your backend |
| displayName | string | No | — | Display name attached to sent messages |
| reconnectAttempts | number | No | 5 | Max WebSocket reconnect attempts |
| reconnectInterval | number | No | 1000 | Milliseconds between reconnect attempts |
| heartbeatInterval | number | No | 30000 | Milliseconds between WebSocket pings |
| timeout | number | No | 10000 | HTTP request timeout in milliseconds |
API Reference
Rooms
// Create a room
const room = await chat.rooms.create({
type: 'dm', // 'dm' | 'group'
members: ['alice', 'bob'],
name: 'Support Chat', // optional, for group rooms
metadata: '{"ticketId": "42"}' // optional JSON string
});
// List rooms the current user belongs to
const rooms = await chat.rooms.list();
// Get a single room
const room = await chat.rooms.get('room_123');
// List members
const members = await chat.rooms.getMembers('room_123');
// Add a member
await chat.rooms.addMember('room_123', 'charlie');Messages
// Send via REST
await chat.messages.send('room_123', 'Hello!');
await chat.messages.send('room_123', 'Hello!', { customKey: 'value' });
// Send via WebSocket (fire-and-forget)
chat.sendMessage('room_123', 'Hello!');
// Fetch message history
const messages = await chat.messages.get('room_123');
const messages = await chat.messages.get('room_123', { after_seq: 10, limit: 50 });
// Acknowledge delivery
await chat.messages.acknowledge('room_123', 42); // REST
chat.acknowledgeMessage('room_123', 42); // WebSocketBots
ChatAPI calls the LLM on the bot's behalf and streams the response back. No agent process required.
// Register a bot.
// llm_api_key_env names the environment variable on the ChatAPI server
// that holds the API key — the key is never stored in the database.
const bot = await chat.bots.create({
name: 'Support Bot',
llm_base_url: 'https://generativelanguage.googleapis.com/v1beta/openai/',
llm_api_key_env: 'GEMINI_API_KEY',
model: 'gemini-2.0-flash',
});
// { bot_id: 'bot_abc123', name: 'Support Bot', ... }
// Add the bot to a room — it responds to every message automatically
await chat.rooms.addMember('room_123', bot.bot_id);System prompt webhook (required for bots)
Set WEBHOOK_URL on the ChatAPI server. Before every LLM call, ChatAPI POSTs to it with type: "bot.context":
// POST https://yourapp.com/api/chatapi/webhook
// Request body:
{
type: 'bot.context',
bot_id: 'bot_abc123',
room_id: 'room_123',
message: { message_id, sender_id, content, created_at },
history: [
{ role: 'user', content: 'Hi there' },
{ role: 'assistant', content: 'Hello! How can I help?' },
// ... up to 20 most-recent messages
]
}
// Your webhook response:
{ system_prompt: 'You are a support agent. Relevant context: ...' }Your webhook runs whatever logic you need — vector search, customer lookup, prompt engineering — and returns the system prompt. The same WEBHOOK_URL also receives type: "message.offline" events for push notifications. ChatAPI passes the system prompt to the LLM, streams the response back via message.stream.* events, and stores the final message.
Manage bots
const bots = await chat.bots.list();
const bot = await chat.bots.get('bot_abc123');
await chat.bots.delete('bot_abc123');Connection Management
await chat.connect();
await chat.disconnect();
chat.isConnected(); // boolean
// Update config at runtime (e.g. after token refresh)
chat.updateConfig({ token: 'new-jwt' });
chat.setDisplayName('Bob');Health Check
const health = await chat.health();
// { status: 'ok', db_writable: true }Real-Time Events
Messages
chat.on('message', (event) => {
// event.room_id, event.message_id, event.sender_id, event.content, event.seq
const displayName = chat.getSenderDisplayName(event);
console.log(`${displayName}: ${event.content}`);
});LLM Streaming
When a bot responds, tokens arrive incrementally via stream events:
chat.on('message.stream.start', (event) => {
// event.room_id, event.message_id, event.sender_id
// stream begins — show a loading indicator
});
chat.on('message.stream.delta', (event) => {
// event.room_id, event.message_id, event.delta
// append delta to the message being built
});
chat.on('message.stream.end', (event) => {
// event.room_id, event.message_id, event.sender_id, event.content, event.seq
// stream complete — full content available
});
chat.on('message.stream.error', (event) => {
// event.room_id, event.message_id
// the LLM call failed — discard any partial content for this message_id
});Typing Indicators
chat.sendTyping('room_123', 'start');
chat.sendTyping('room_123', 'stop');
chat.on('typing', (event) => {
// event.room_id, event.user_id, event.action: 'start' | 'stop'
});Presence
chat.on('presence.update', (event) => {
// event.user_id, event.status: 'online' | 'offline'
});Delivery Acknowledgements
chat.on('ack.received', (event) => {
// event.room_id, event.seq, event.user_id
});Connection Lifecycle
chat.on('connection.open', () => { /* connected */ });
chat.on('connection.lost', () => { /* dropped */ });
chat.on('connection.reconnecting', (event) => {
console.log('Reconnect attempt', event.attempt);
});
chat.on('connection.failed', () => { /* gave up after max attempts */ });
chat.on('server.shutdown', (event) => {
// event.reconnect_after_ms — server is restarting
});
// Remove a specific listener
chat.off('message', handler);Error Handling
import {
ChatAPIError,
AuthenticationError,
ValidationError,
ConnectionError,
} from '@hastenr/chatapi-sdk';
try {
await chat.messages.send('room_123', 'Hello');
} catch (err) {
if (err instanceof AuthenticationError) {
// 401 — token invalid or expired
} else if (err instanceof ValidationError) {
// 400 — bad request
console.log(err.details?.field);
} else if (err instanceof ConnectionError) {
// WebSocket failed to connect
} else if (err instanceof ChatAPIError) {
console.log(err.code, err.statusCode, err.details);
}
}Running ChatAPI
The SDK connects to a self-hosted ChatAPI server. The fastest way to get one running:
docker run -d \
-p 8080:8080 \
-e JWT_SECRET=$(openssl rand -base64 32) \
-e ALLOWED_ORIGINS="*" \
hastenr/chatapi:latestSee the ChatAPI repository for full deployment options and configuration.
License
MIT — see LICENSE for details.
