npm package discovery and stats viewer.

Discover Tips

  • General search

    [free text search, go nuts!]

  • Package details

    pkg:[package-name]

  • User packages

    @[username]

Sponsor

Optimize Toolset

I’ve always been into building performant and accessible sites, but lately I’ve been taking it extremely seriously. So much so that I’ve been building a tool to help me optimize and monitor the sites that I build to make sure that I’m making an attempt to offer the best experience to those who visit them. If you’re into performant, accessible and SEO friendly sites, you might like it too! You can check it out at Optimize Toolset.

About

Hi, 👋, I’m Ryan Hefner  and I built this site for me, and you! The goal of this site was to provide an easy way for me to check the stats on my npm packages, both for prioritizing issues and updates, and to give me a little kick in the pants to keep up on stuff.

As I was building it, I realized that I was actually using the tool to build the tool, and figured I might as well put this out there and hopefully others will find it to be a fast and useful way to search and browse npm packages as I have.

If you’re interested in other things I’m working on, follow me on Twitter or check out the open source projects I’ve been publishing on GitHub.

I am also working on a Twitter bot for this site to tweet the most popular, newest, random packages from npm. Please follow that account now and it will start sending out packages soon–ish.

Open Software & Tools

This site wouldn’t be possible without the immense generosity and tireless efforts from the people who make contributions to the world and share their work via open source initiatives. Thank you 🙏

© 2026 – Pkg Stats / Ryan Hefner

@hastenr/chatapi-sdk

v0.1.13

Published

Official ChatAPI TypeScript SDK for real-time messaging

Readme

@hastenr/chatapi-sdk

npm License: MIT TypeScript

The official TypeScript SDK for ChatAPI — self-hosted, real-time messaging infrastructure for apps where AI is a participant.

Installation

npm install @hastenr/chatapi-sdk

Quick 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);            // WebSocket

Bots

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:latest

See the ChatAPI repository for full deployment options and configuration.

License

MIT — see LICENSE for details.