@northbound-run/chat-adapter-matrix-agent
v0.7.0
Published
Matrix adapter for the Vercel Chat SDK. Works with any Chat SDK host (Mastra, bare Chat SDK, etc.) via a single createMatrixAgentAdapter().
Maintainers
Readme
@northbound-run/chat-adapter-matrix-agent
Matrix adapter for the Vercel Chat SDK. Sibling to @chat-adapter/slack, @chat-adapter/discord, @chat-adapter/telegram — drop it into any Chat SDK host (Mastra, bare Chat SDK, or custom bot framework) and it covers every platform AgentChannels' Matrix substrate bridges: Slack, Telegram, Discord, Signal, WhatsApp, iMessage, plain Matrix.
Overview
Implements the chat@^4.24 Adapter<TThreadId, TRawMessage> interface. One platform-agnostic createMatrixAgentAdapter() reads Matrix sync events and dispatches them to the Chat SDK's ChatInstance.processMessage; outbound operations delegate to @northbound-run/matrix-agent-sdk.
Because AgentChannels' Matrix substrate normalizes every bridged platform into m.room.message events, a single adapter instance handles everything the official @chat-adapter/* packages cover — plus Signal, WhatsApp, and iMessage, which Vercel Chat SDK does not ship adapters for.
Quick Start
With bare Chat SDK
import { Chat } from "chat";
import { AgentChannelsMatrixClient } from "@northbound-run/matrix-agent-sdk";
import { createMatrixAgentAdapter } from "@northbound-run/chat-adapter-matrix-agent";
const matrix = new AgentChannelsMatrixClient({
accessToken: process.env.MATRIX_ACCESS_TOKEN!,
userId: process.env.MATRIX_USER_ID!,
homeserverUrl: "https://matrix.agentchannels.dev",
});
await matrix.start();
const bot = new Chat({
userName: "support-bot",
adapters: {
matrix: createMatrixAgentAdapter({ client: matrix }),
},
});
bot.onNewMention(async (thread, message) => {
await thread.post(`You said: ${message.text}`);
});With Mastra
import { Agent } from "@mastra/core/agent";
import { Mastra } from "@mastra/core";
import { AgentChannelsMatrixClient } from "@northbound-run/matrix-agent-sdk";
import { createMatrixAgentAdapter } from "@northbound-run/chat-adapter-matrix-agent";
const matrix = new AgentChannelsMatrixClient({
accessToken: process.env.MATRIX_ACCESS_TOKEN!,
userId: process.env.MATRIX_USER_ID!,
homeserverUrl: "https://matrix.agentchannels.dev",
});
await matrix.start();
export const supportAgent = new Agent({
id: "support-agent",
name: "Support Agent",
instructions: "You are a helpful support assistant.",
model: "openai/gpt-4.1-mini",
channels: {
adapters: {
matrix: createMatrixAgentAdapter({ client: matrix }),
},
},
});
export const mastra = new Mastra({ agents: { supportAgent } });Invite the bot to a Matrix room bridged to your target platform. Every @bot-mention surfaces on the Chat SDK's event handlers (onNewMention / onSubscribedMessage); replies are posted back through the bridge.
See examples/mastra-basic.ts for a full runnable Mastra fixture.
Installation
bun add @northbound-run/chat-adapter-matrix-agent
# or
npm install @northbound-run/chat-adapter-matrix-agentPeers required:
chat^4.24— the Vercel Chat SDK.@mastra/core^1.22— optional. Only needed when consumed from Mastra; skip it for bare Chat SDK apps.
Workspace runtime dep (managed):
@northbound-run/matrix-agent-sdk— provides theAgentChannelsMatrixClient.
Configuration
The adapter accepts a single MatrixAgentAdapterOptions record:
| Field | Type | Default | Description |
|---|---|---|---|
| client | AgentChannelsMatrixClient | — | BYO Matrix client (already .start()-ed). If omitted, the adapter constructs one from credentials / env vars / FileStore. |
| credentials | StoredCredentials | — | Explicit {accessToken, userId, deviceId?, homeserverUrl}. Ignored when client is set. |
| homeserverUrl | string | https://matrix.agentchannels.dev | Override the SDK default. |
| storePath | string | ~/.matrix-agent | Override the SDK's FileStore location. |
| userName | string | MXID localpart | Bot username surfaced to the Chat SDK. |
| bridgeBotFilter | string[] \| (userId: string) => boolean | SDK's isBridgeBotMxid | Override the echo filter. A string array filters by exact MXID; a function is called per sender. |
| inlineMediaMimePatterns | string[] | [] | MIME patterns to resolve inline (planned for richer attachment handling). |
| lockScope | "thread" \| "channel" | "thread" | Mapped 1:1 to the Chat SDK Adapter.lockScope. Use "channel" for WhatsApp/Telegram-style serialization. |
| approvalTimeoutMs | number | 300_000 (5 min) | How long a requireApproval: true tool gate waits before auto-denying. |
| logger | chat.Logger | undefined | Chat SDK logger. Receives warn/error strings for reconnect and rate-limit events. |
API Reference
createMatrixAgentAdapter(options)
Factory returning a MatrixAgentAdapter — assignable to Adapter<MatrixThreadData, MatrixRawMessage>.
MatrixAgentAdapter
Implements the Vercel Chat SDK Adapter contract. See Not Implemented in V1 for the methods deliberately absent.
Thread-ID codec
import {
encodeThreadId,
decodeThreadId,
channelIdFromThreadId,
threadDataFromMessage,
TOP_LEVEL_ROOT,
} from "@northbound-run/chat-adapter-matrix-agent";Thread IDs are matrix:<base64url(roomId)>:<rootEventId | "top">. The codec splits on the first two colons only, so Matrix event IDs (shape $hash:server, which contain colons) round-trip intact.
Format translation
import { renderFormatted, htmlToMdast } from "@northbound-run/chat-adapter-matrix-agent";
renderFormatted(mdast); // { html, plain }
htmlToMdast(matrixHtml); // mdast RootMatrix HTML whitelist: <b>/<strong>, <i>/<em>, <code>, <pre><code>, <a href>, <ul>/<ol>/<li>, <blockquote>, <br>. Unknown tags and entities outside the five canonical ones are dropped.
Channel / DM classification
import { ChannelVisibilityResolver, classifyRoom } from "@northbound-run/chat-adapter-matrix-agent";Slack rooms go through the Beeper m.bridge state event. Everything else falls back to a 2-member-joined-room heuristic. Signal / WhatsApp / Telegram / iMessage / Discord bridges that adopt the Beeper convention automatically participate via the SDK's SUPPORTED_BRIDGE_PROTOCOLS set.
Approval state machine
import { ApprovalRegistry, APPROVE_PATTERNS, DENY_PATTERNS } from "@northbound-run/chat-adapter-matrix-agent";Tools marked requireApproval: true post a plain-text prompt like 🔐 Approval needed for <toolName>: <preview>. Reply "approve" / "yes" / 👍 or "deny" / "no" / 👎 within 5 minutes.. Replies are matched FIFO per thread. Non-matching replies pass through to the agent as a normal turn.
Error Handling
Outbound operations emit every error type @northbound-run/matrix-agent-sdk defines: AuthError, TokenExpiredError, RateLimitError, NetworkError, RoomNotFoundError, InsufficientPowerLevelError, etc. See matrix-agent-sdk's README for the full taxonomy.
The adapter auto-retries RateLimitError once when retryAfterMs <= 30000; longer waits rethrow so the Chat SDK's concurrency / onLockConflict hooks can decide what to do.
Runtime
- Bun (primary) and Node 22+.
- The package emits pure ESM (
"type": "module"); CJS consumers need a wrapper. - Requires a long-running host. Sync-loop mode holds a persistent Matrix connection; Vercel Functions / Netlify Functions / Cloudflare Workers are not supported in V1.
Not Implemented in V1
The following optional Chat SDK Adapter methods are deliberately absent. The Chat SDK handles missing optional methods gracefully — see the Chat SDK docs for the fallback each triggers.
| Method | Why absent | V2 plan |
|---|---|---|
| scheduleMessage | Matrix has no native scheduled-send primitive. | Client-side timer + postMessage at the scheduled time. |
| postObject / editObject | Plans / Polls render to platform-native Block Kit / inline keyboards; Matrix has no equivalent. | Render to Matrix HTML with reaction-based voting. |
| rehydrateAttachment | Only relevant for concurrency: "queue" / "debounce" workloads where attachments are serialized. | Lift the fetch-closure out of parseMessage so rehydration works via roomId + mxc. |
| native stream | Slack-specific streaming API. | m.in_reply_to edit-based streaming with update throttling. |
| openModal | Matrix has no Block Kit modal equivalent. | Reaction-based approval cards (👍/👎 on a proposal message). |
| postEphemeral | Matrix has no ephemeral-visibility primitive. | Silent DM fallback via openDM + postMessage. |
V1 Known Limitations
- End-to-end encrypted rooms — the adapter early-returns on
m.room.encryptedevents with a warn log; sends into encrypted rooms will fail. Invite the bot only to unencrypted rooms untilmatrix-agent-sdkgains E2EE support. - Serverless hosts — the sync-loop model requires a long-running process.
- Legacy
m.in_reply_to— Matrix replies outside the modern threading model route into the main timeline (no thread ID derived). Reply context is preserved onMessage.raw.normalized.replyTofor advanced consumers.
Tier
Community adapter. The @chat-adapter/* npm scope is reserved for Vercel's official adapters. This package publishes under our own @northbound-run/* scope.
License
MIT
