@northbound-run/matrix-agent-sdk
v0.7.0
Published
Core TypeScript SDK for building Matrix agents. Provides a high-level, strongly-typed client that wraps `matrix-js-sdk` with normalized event types, credential management, and multiple storage backends.
Readme
@northbound-run/matrix-agent-sdk
Core TypeScript SDK for building Matrix agents. Provides a high-level, strongly-typed client that wraps matrix-js-sdk with normalized event types, credential management, and multiple storage backends.
Overview
@northbound-run/matrix-agent-sdk is a thin, opinionated layer over matrix-js-sdk designed for agent use cases. It normalizes Matrix event types into common message, reaction, redaction, and membership events; manages persistent credentials and sync state; and provides a typed event emitter for real-time updates.
The client exposes eight sub-APIs: messages (send, edit, redact, react, mark read), rooms (list, create, join, leave, invite, get info, set topic/name), members (list, kick, ban, unban, set power level), state (read and write room state events), media (upload, download), typing (send typing indicators), receipts (read receipts), and account (profile, presence).
Quick Start
Login with password:
import { AgentChannelsMatrixClient } from "@northbound-run/matrix-agent-sdk";
const client = await AgentChannelsMatrixClient.login({
username: "mybot",
password: "secret",
homeserverUrl: "https://matrix.org",
});
await client.start();Connect with existing credentials:
const client = new AgentChannelsMatrixClient({
accessToken: "syt_...",
userId: "@mybot:matrix.org",
deviceId: "MYBOT_DEVICE",
homeserverUrl: "https://matrix.org",
});
await client.start();Load stored credentials:
import { loadFirstCredential } from "@northbound-run/matrix-agent-sdk";
const creds = await loadFirstCredential({
storePath: "~/.matrix-agent",
});
const client = new AgentChannelsMatrixClient(creds);
await client.start();Listen for events:
client.on("message", (event) => {
console.log(`${event.sender}: ${event.body}`);
});
client.on("reaction", (event) => {
console.log(`${event.sender} reacted with ${event.content.shortcode}`);
});
client.on("room.join", (event) => {
console.log(`Joined room: ${event.roomId}`);
});Send a message:
await client.messages.send({
roomId: "!abc:matrix.org",
body: "Hello, world!",
});Installation
npm install @northbound-run/matrix-agent-sdk
# or
bun install @northbound-run/matrix-agent-sdkRequires matrix-js-sdk ^35.0.0 (installed automatically).
Configuration
AgentChannelsMatrixClient Constructor
| Parameter | Type | Default | Description |
|---|---|---|---|
| accessToken | string | — | Access token for the Matrix account |
| userId | string | — | Full Matrix user ID (e.g. @bot:matrix.org) |
| deviceId | string | — | Device ID for the session |
| homeserverUrl | string | https://matrix.agentchannels.dev | Matrix homeserver URL |
| store | string or IAgentStore | "memory" | Storage backend: "memory", "file", "sqlite", or custom store |
| storePath | string | ~/.matrix-agent | Path for file-based storage |
Login Configuration
| Parameter | Type | Required | Description |
|---|---|---|---|
| username | string | Yes | Local part of the user ID (no @ or server) |
| password | string | Yes | Account password |
| homeserverUrl | string | No | Homeserver URL (defaults to https://matrix.agentchannels.dev) |
| store | string or IAgentStore | No | Storage backend (defaults to "memory") |
| storePath | string | No | Path for file-based storage |
API Reference
AgentEventEmitter
Typed event emitter with the following events:
| Event | Payload | Description |
|---|---|---|
| message | NormalizedMessage | Text or formatted message received |
| message.edit | NormalizedMessage | Message edited by sender |
| message.redact | NormalizedRedaction | Message redacted (deleted) |
| reaction | NormalizedReaction | Emoji reaction added to a message |
| reaction.redact | NormalizedReaction | Reaction removed |
| room.join | RoomInfo | Client joined a room |
| room.leave | RoomInfo | Client left a room |
| room.invite | RoomInfo | Client invited to a room (not yet joined) |
| room.update | RoomInfo | Room name, topic, or encryption status changed |
| room.encrypted | RoomInfo | Room encryption toggled |
| member.join | AgentMembership | Another user joined a room |
| member.leave | AgentMembership | Another user left a room |
| member.invite | AgentMembership | Another user invited to a room |
| typing | TypingEvent | User is typing indicator |
| receipt | ReceiptEvent | Read receipt from user |
| connection | ConnectionState | Sync connection opened or closed |
| sync | AgentSyncState | Sync completed (initial or incremental) |
Usage:
client.on("message", (msg) => {
console.log(msg.body);
});
client.once("room.join", (room) => {
console.log(`Joined ${room.name}`);
});Messages API
Send a message:
await client.messages.send({
roomId: "!abc:matrix.org",
body: "Hello",
format: "plain", // or "html"
msgtype: "m.text", // or "m.notice", "m.emote"
});Edit a message:
await client.messages.edit({
roomId: "!abc:matrix.org",
eventId: "$old_event",
body: "Updated text",
});Redact a message:
await client.messages.redact({
roomId: "!abc:matrix.org",
eventId: "$event",
reason: "Spam",
});React to a message:
await client.messages.react({
roomId: "!abc:matrix.org",
eventId: "$event",
emoji: "👍",
});Mark as read:
await client.messages.markRead({
roomId: "!abc:matrix.org",
eventId: "$event",
});Get recent messages:
const messages = await client.messages.getMessages({
roomId: "!abc:matrix.org",
limit: 50,
direction: "b", // "b" for backwards, "f" for forwards
});Rooms API
List rooms:
const rooms = await client.rooms.list();Create a room:
await client.rooms.create({
name: "My Room",
topic: "Discussion",
preset: "private_chat", // or "public_chat", "trusted_private_chat"
invites: ["@user:matrix.org"],
});Join a room:
await client.rooms.join({
roomId: "!abc:matrix.org", // or use room alias "#name:server"
});Leave a room:
await client.rooms.leave({
roomId: "!abc:matrix.org",
});Invite a user:
await client.rooms.invite({
roomId: "!abc:matrix.org",
userId: "@user:matrix.org",
});Get room info:
const info = await client.rooms.getInfo({
roomId: "!abc:matrix.org",
});
// info.name, info.topic, info.memberCount, info.encryptedSet room topic:
await client.rooms.setTopic({
roomId: "!abc:matrix.org",
topic: "New topic",
});Set room name:
await client.rooms.setName({
roomId: "!abc:matrix.org",
name: "New name",
});Members API
List members:
const members = await client.members.list({
roomId: "!abc:matrix.org",
});Kick a member:
await client.members.kick({
roomId: "!abc:matrix.org",
userId: "@user:matrix.org",
reason: "Spam",
});Ban a member:
await client.members.ban({
roomId: "!abc:matrix.org",
userId: "@user:matrix.org",
reason: "Spam",
});Unban a member:
await client.members.unban({
roomId: "!abc:matrix.org",
userId: "@user:matrix.org",
});Set power level:
await client.members.setPowerLevel({
roomId: "!abc:matrix.org",
userId: "@user:matrix.org",
powerLevel: 50, // 0-100, default is 0, 50 is moderator, 100 is admin
});State API
Get a state event:
const event = await client.state.get({
roomId: "!abc:matrix.org",
type: "m.room.topic",
stateKey: "", // optional state key
});Set a state event:
await client.state.set({
roomId: "!abc:matrix.org",
type: "m.room.custom_event",
content: { custom: "data" },
stateKey: "custom_key", // optional
});Get all state:
const allState = await client.state.getAll({
roomId: "!abc:matrix.org",
});Media API
Upload media:
const url = await client.media.upload({
data: Buffer.from("..."), // or Uint8Array
mimeType: "image/png",
filename: "image.png",
});
// url is an MXC URL: "mxc://server/media_id"Download media:
const data = await client.media.download({
mxcUrl: "mxc://matrix.org/abc123",
});
// returns Uint8ArrayGet HTTPS URL for media:
const httpsUrl = await client.media.getUrl({
mxcUrl: "mxc://matrix.org/abc123",
});
// returns "https://matrix.org/_matrix/media/r0/download/matrix.org/abc123"Typing API
Send typing indicator:
await client.typing.set({
roomId: "!abc:matrix.org",
typing: true,
timeout: 5000, // milliseconds
});Receipts API
Get read receipts:
const receipts = await client.receipts.get({
roomId: "!abc:matrix.org",
eventId: "$event",
});Account API
Get profile:
const profile = await client.account.getProfile({
userId: "@user:matrix.org", // optional, defaults to current user
});
// profile.displayName, profile.avatarUrlSet display name:
await client.account.setDisplayName({
displayName: "My Bot",
});Set avatar:
await client.account.setAvatar({
mxcUrl: "mxc://matrix.org/abc123",
});Set presence:
await client.account.setPresence({
presence: "online", // "online", "offline", "unavailable"
statusMessage: "In a meeting",
});Credential Management
loadCredentials
Load an array of credentials (multi-account support). Credentials are sourced from (in order):
- Credentials file at the specified path
MATRIX_*environment variables (single account)- FileStore at the default location
import { loadCredentials } from "@northbound-run/matrix-agent-sdk";
const creds = await loadCredentials({
storePath: "~/.matrix-agent",
});
// returns StoredCredentials[] (may be empty)loadFirstCredential
Load the first available credential. Throws if no credentials found.
import { loadFirstCredential } from "@northbound-run/matrix-agent-sdk";
const cred = await loadFirstCredential({
storePath: "~/.matrix-agent",
});saveCredentials
Persist credentials to the file store.
import { saveCredentials } from "@northbound-run/matrix-agent-sdk";
await saveCredentials("~/.matrix-agent", [
{
userId: "@bot:matrix.org",
accessToken: "syt_...",
deviceId: "BOT_DEVICE",
homeserverUrl: "https://matrix.org",
},
]);listStoreAccounts
List user IDs that have stored credentials.
import { listStoreAccounts } from "@northbound-run/matrix-agent-sdk";
const userIds = await listStoreAccounts("~/.matrix-agent");validateCredentials
Type guard to validate an object as StoredCredentials.
import { validateCredentials } from "@northbound-run/matrix-agent-sdk";
if (validateCredentials(obj)) {
// obj is StoredCredentials
}resolveStorePath
Resolve a path with ~ to absolute path.
import { resolveStorePath } from "@northbound-run/matrix-agent-sdk";
const absolute = resolveStorePath("~/.matrix-agent");Storage Backends
MemoryStore (default)
In-memory store for ephemeral sessions. Data is lost on shutdown.
const client = new AgentChannelsMatrixClient({
accessToken: "token",
userId: "@bot:matrix.org",
deviceId: "BOT",
store: "memory",
});FileStore
File-based JSON store for persistent sessions. Stores credentials, sync token, and sync cache.
const client = new AgentChannelsMatrixClient({
accessToken: "token",
userId: "@bot:matrix.org",
deviceId: "BOT",
store: "file",
storePath: "~/.matrix-agent",
});Custom Store
Implement IAgentStore to use a custom storage backend (e.g. database, encrypted storage).
import type { IAgentStore } from "@northbound-run/matrix-agent-sdk";
class CustomStore implements IAgentStore {
async getCredentials(): Promise<StoredCredentials | null> { /* ... */ }
async setCredentials(creds: StoredCredentials): Promise<void> { /* ... */ }
async getSyncToken(): Promise<string | null> { /* ... */ }
async setSyncToken(token: string): Promise<void> { /* ... */ }
async getSyncCache(): Promise<Record<string, unknown> | null> { /* ... */ }
async setSyncCache(cache: Record<string, unknown>): Promise<void> { /* ... */ }
}
const client = new AgentChannelsMatrixClient({
accessToken: "token",
userId: "@bot:matrix.org",
deviceId: "BOT",
store: new CustomStore(),
});Error Handling
The SDK exports a typed error hierarchy. Catch specific errors to handle them appropriately:
import {
AgentChannelsMatrixError,
AuthError,
RoomNotFoundError,
TokenExpiredError,
RateLimitError,
NetworkError,
} from "@northbound-run/matrix-agent-sdk";
try {
await client.messages.send({
roomId: "!abc:matrix.org",
body: "Hello",
});
} catch (err) {
if (err instanceof TokenExpiredError) {
console.error("Re-authenticate required");
} else if (err instanceof RateLimitError) {
console.error(`Rate limited, retry after ${err.retryAfter}ms`);
} else if (err instanceof RoomNotFoundError) {
console.error(`Room not found: ${err.roomId}`);
} else if (err instanceof AuthError) {
console.error("Authentication failed");
} else if (err instanceof NetworkError) {
console.error("Network error, will retry");
} else if (err instanceof AgentChannelsMatrixError) {
console.error(`Matrix error: ${err.message}`);
}
}Error Classes
AgentChannelsMatrixError— Base error classAuthError— Authentication failedInvalidCredentialsError— Credentials invalid or missingTokenExpiredError— Access token expiredRateLimitError— Rate limited by homeserver (includesretryAfter)NetworkError— Network connection failedTimeoutError— Request timed outHomeserverError— Homeserver returned an errorRoomNotFoundError— Room does not existNotInRoomError— Client not a member of the roomInsufficientPowerLevelError— Insufficient permissions for operationEventNotFoundError— Event does not existInvalidMessageError— Message body or format invalidStoreError— Storage operation failedSyncError— Sync connection failedNotReadyError— Client not connected (callstart()first)
Constants
import {
DEFAULT_HOMESERVER_URL,
DEFAULT_STORE_PATH,
} from "@northbound-run/matrix-agent-sdk";
console.log(DEFAULT_HOMESERVER_URL); // "https://matrix.agentchannels.dev"
console.log(DEFAULT_STORE_PATH); // "~/.matrix-agent"Runtime
Supports Node.js and Bun. Requires no runtime-specific dependencies beyond matrix-js-sdk.
License
MIT
