npm package: @hastenr/chatapi-sdk
Description: Official ChatAPI TypeScript SDK for real-time messaging
Author: ChatAPI Team
Latest version: 0.1.9

# @hastenr/chatapi-sdk

[![npm](https://img.shields.io/npm/v/@hastenr/chatapi-sdk?style=flat-square&color=00ED64&labelColor=001E2B)](https://www.npmjs.com/package/@hastenr/chatapi-sdk)
[![License: MIT](https://img.shields.io/badge/license-MIT-00ED64?style=flat-square&labelColor=001E2B)](LICENSE)
[![TypeScript](https://img.shields.io/badge/TypeScript-strict-00ADD8?style=flat-square&labelColor=001E2B)](https://www.typescriptlang.org/)

The official TypeScript SDK for [ChatAPI](https://github.com/hastenr/chatapi) — self-hosted, real-time messaging infrastructure for apps where AI is a participant.

## Installation

```bash
npm install @hastenr/chatapi-sdk
```

## Quick Start

```typescript
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

```typescript
// 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

```typescript
// 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

Bots are AI participants that join rooms like regular users. Two modes are supported:

- **`llm`** — ChatAPI calls the LLM directly, handles context and streaming
- **`external`** — your own process connects via JWT and handles the logic

```typescript
// Register an LLM bot
const bot = await chat.bots.create({
  name: 'Support Bot',
  mode: 'llm',
  provider: 'openai',            // 'openai' | 'anthropic' or any OpenAI-compatible
  model: 'gpt-4o',
  api_key: 'sk-...',
  system_prompt: 'You are a helpful support agent.',
  max_context: 20,               // number of messages to include as context
});

// Register an external bot (your process handles the logic)
const bot = await chat.bots.create({
  name: 'My Agent',
  mode: 'external',
});

const bots = await chat.bots.list();
const bot  = await chat.bots.get('bot_123');
await chat.bots.delete('bot_123');
```

### Connection Management

```typescript
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

```typescript
const health = await chat.health();
// { status: 'ok', db_writable: true }
```

## Real-Time Events

### Messages

```typescript
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:

```typescript
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.content, event.seq
  // stream complete — full content available
});
```

### Typing Indicators

```typescript
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

```typescript
chat.on('presence.update', (event) => {
  // event.user_id, event.status: 'online' | 'offline'
});
```

### Delivery Acknowledgements

```typescript
chat.on('ack.received', (event) => {
  // event.room_id, event.seq, event.user_id
});
```

### Connection Lifecycle

```typescript
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

```typescript
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:

```bash
docker run -d \
  -p 8080:8080 \
  -e JWT_SECRET=$(openssl rand -base64 32) \
  -e ALLOWED_ORIGINS="*" \
  hastenr/chatapi:latest
```

See the [ChatAPI repository](https://github.com/hastenr/chatapi) for full deployment options and configuration.

## License

MIT — see [LICENSE](LICENSE) for details.