Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
33 changes: 33 additions & 0 deletions .changeset/add-signal-adapter.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,33 @@
---
"@chat-adapter/signal": minor
"chat": minor
---

Add a new `@chat-adapter/signal` package for Signal bots powered by `signal-cli-rest-api`.

**Adapter features:**

- Incoming updates via webhook (including JSON-RPC receive payloads), REST polling (`pollOnce`/`startPolling`/`stopPolling`), and WebSocket (json-rpc mode) support
- Message send/edit/delete via `/v2/send` and `/v1/remote-delete`
- Reactions (add/remove) via `/v1/reactions`
- Typing indicators via `/v1/typing-indicator`
- File attachments (incoming metadata + lazy download, outgoing base64 data URIs)
- DM and group thread handling with `group.` prefix convention
- Cached message fetch APIs (`fetchMessages`/`fetchMessage`/`fetchChannelMessages`) matching Telegram adapter's in-memory cache style
- Message length truncation (4096 characters, matching Telegram)
- `text_mode` support (`normal`/`styled`) for Signal's markdown formatting

**Reliability & correctness:**

- Fail-fast initialization: health check (`/v1/health`) and account verification (`/v1/accounts`) during `initialize()`
- Incoming edit messages dispatched through `chat.processMessage` with stable message IDs across identity alias evolution
- Sync sent messages from linked devices routed through `chat.processMessage`
- Remote delete events remove messages from cache
- Identity canonicalization: phone number/UUID/source aliases tracked and canonicalized, preferring phone format
- Deterministic group ID normalization (inbound binary→base64, outbound validation)
- Full error mapping: 401→`AuthenticationError`, 403→`PermissionError`, 404→`ResourceNotFoundError`, 429→`AdapterRateLimitError`, 400→`ValidationError`, 5xx→`NetworkError`

**Chat SDK core changes:**

- Signal-aware user ID inference in `chat.openDM()` for `signal:...` prefixed IDs and E.164 phone numbers
- Message deduplication key includes edit revision suffix (`editedAt` timestamp) so edited messages are not swallowed as duplicates
6 changes: 4 additions & 2 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@
[![npm downloads](https://img.shields.io/npm/dm/chat)](https://www.npmjs.com/package/chat)
[![License: MIT](https://img.shields.io/badge/License-MIT-blue.svg)](https://opensource.org/licenses/MIT)

A unified TypeScript SDK for building chat bots across Slack, Microsoft Teams, Google Chat, Discord, Telegram, GitHub, and Linear. Write your bot logic once, deploy everywhere.
A unified TypeScript SDK for building chat bots across Slack, Microsoft Teams, Google Chat, Discord, Telegram, Signal, GitHub, and Linear. Write your bot logic once, deploy everywhere.

## Installation

Expand All @@ -15,7 +15,7 @@ npm install chat
Install adapters for your platforms:

```bash
npm install @chat-adapter/slack @chat-adapter/teams @chat-adapter/gchat @chat-adapter/discord @chat-adapter/telegram
npm install @chat-adapter/slack @chat-adapter/teams @chat-adapter/gchat @chat-adapter/discord @chat-adapter/telegram @chat-adapter/signal
```

## Usage
Expand Down Expand Up @@ -54,6 +54,7 @@ See the [Getting Started guide](https://chat-sdk.dev/docs/getting-started) for a
| Google Chat | `@chat-adapter/gchat` | Yes | Yes | Yes | No | Post+Edit | Yes |
| Discord | `@chat-adapter/discord` | Yes | Yes | Yes | No | Post+Edit | Yes |
| Telegram | `@chat-adapter/telegram` | Yes | Yes | Partial | No | Post+Edit | Yes |
| Signal | `@chat-adapter/signal` | Yes | Yes | Fallback text | No | Post+Edit | Yes |
| GitHub | `@chat-adapter/github` | Yes | Yes | No | No | No | No |
| Linear | `@chat-adapter/linear` | Yes | Yes | No | No | No | No |

Expand All @@ -80,6 +81,7 @@ See the [Getting Started guide](https://chat-sdk.dev/docs/getting-started) for a
| `@chat-adapter/gchat` | [Google Chat adapter](https://chat-sdk.dev/docs/adapters/gchat) |
| `@chat-adapter/discord` | [Discord adapter](https://chat-sdk.dev/docs/adapters/discord) |
| `@chat-adapter/telegram` | [Telegram adapter](https://chat-sdk.dev/docs/adapters/telegram) |
| `@chat-adapter/signal` | [Signal adapter](https://chat-sdk.dev/docs/adapters/signal) |
| `@chat-adapter/github` | [GitHub adapter](https://chat-sdk.dev/docs/adapters/github) |
| `@chat-adapter/linear` | [Linear adapter](https://chat-sdk.dev/docs/adapters/linear) |
| `@chat-adapter/state-redis` | [Redis state adapter](https://chat-sdk.dev/docs/state/redis) (production) |
Expand Down
16 changes: 8 additions & 8 deletions apps/docs/app/[lang]/sitemap.md/route.ts
Original file line number Diff line number Diff line change
Expand Up @@ -13,17 +13,17 @@ export const generateStaticParams = async () => {
const DOCS_PREFIX_PATTERN = /^\/docs\/?/;
const WHITESPACE_PATTERN = /\s+/;

type PageNode = {
title: string;
interface PageNode {
children: PageNode[];
description: string;
url: string;
type?: string;
summary?: string;
lastmod?: string;
prerequisites?: string[];
product?: string;
lastmod?: string;
children: PageNode[];
};
summary?: string;
title: string;
type?: string;
url: string;
}

function buildTree(
pages: Array<{
Expand Down
1 change: 1 addition & 0 deletions examples/nextjs-chat/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,7 @@
"@chat-adapter/gchat": "workspace:*",
"@chat-adapter/github": "workspace:*",
"@chat-adapter/linear": "workspace:*",
"@chat-adapter/signal": "workspace:*",
"@chat-adapter/slack": "workspace:*",
"@chat-adapter/state-memory": "workspace:*",
"@chat-adapter/state-redis": "workspace:*",
Expand Down
12 changes: 12 additions & 0 deletions examples/nextjs-chat/src/app/page.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -21,6 +21,9 @@ export default function Home() {
<li>
<code>/api/webhooks/telegram</code> - Telegram bot updates
</li>
<li>
<code>/api/webhooks/signal</code> - Signal bot updates
</li>
<li>
<code>/api/webhooks/github</code> - GitHub PR comment events
</li>
Expand Down Expand Up @@ -78,6 +81,15 @@ DISCORD_APPLICATION_ID=...`}
TELEGRAM_WEBHOOK_SECRET_TOKEN=...`}
</pre>

<h3>Signal</h3>
<pre>
{`SIGNAL_PHONE_NUMBER=+1234567890
SIGNAL_SERVICE_URL=http://localhost:8080

# Configure signal-cli-rest-api to forward updates:
RECEIVE_WEBHOOK_URL=https://your-app.com/api/webhooks/signal`}
</pre>

<h3>GitHub</h3>
<pre>
{`# PAT auth (simple)
Expand Down
25 changes: 25 additions & 0 deletions examples/nextjs-chat/src/lib/adapters.ts
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@ import {
} from "@chat-adapter/gchat";
import { createGitHubAdapter, type GitHubAdapter } from "@chat-adapter/github";
import { createLinearAdapter, type LinearAdapter } from "@chat-adapter/linear";
import { createSignalAdapter, type SignalAdapter } from "@chat-adapter/signal";
import { createSlackAdapter, type SlackAdapter } from "@chat-adapter/slack";
import { createTeamsAdapter, type TeamsAdapter } from "@chat-adapter/teams";
import {
Expand All @@ -25,6 +26,7 @@ export interface Adapters {
gchat?: GoogleChatAdapter;
github?: GitHubAdapter;
linear?: LinearAdapter;
signal?: SignalAdapter;
slack?: SlackAdapter;
teams?: TeamsAdapter;
telegram?: TelegramAdapter;
Expand Down Expand Up @@ -86,6 +88,16 @@ const LINEAR_METHODS = [
"addReaction",
"fetchMessages",
];
const SIGNAL_METHODS = [
"postMessage",
"editMessage",
"deleteMessage",
"addReaction",
"removeReaction",
"startTyping",
"openDM",
"fetchMessages",
];
const TELEGRAM_METHODS = [
"postMessage",
"editMessage",
Expand Down Expand Up @@ -204,6 +216,19 @@ export function buildAdapters(): Adapters {
}
}

// Signal adapter (optional) - env vars: SIGNAL_PHONE_NUMBER (+ optional SIGNAL_SERVICE_URL)
if (process.env.SIGNAL_PHONE_NUMBER) {
adapters.signal = withRecording(
createSignalAdapter({
phoneNumber: process.env.SIGNAL_PHONE_NUMBER,
baseUrl: process.env.SIGNAL_SERVICE_URL ?? process.env.SIGNAL_SERVICE,
logger: logger.child("signal"),
}),
"signal",
SIGNAL_METHODS
);
}

// Telegram adapter (optional) - env vars: TELEGRAM_BOT_TOKEN
if (process.env.TELEGRAM_BOT_TOKEN) {
adapters.telegram = withRecording(
Expand Down
8 changes: 7 additions & 1 deletion examples/nextjs-chat/src/lib/bot.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -638,7 +638,13 @@ bot.onNewMessage(/help/i, async (thread, message) => {

// Handle messages in subscribed threads
bot.onSubscribedMessage(async (thread, message) => {
if (!(thread.adapter.name === "telegram" || message.isMention)) {
if (
!(
thread.adapter.name === "telegram" ||
thread.adapter.name === "signal" ||
message.isMention
)
) {
return;
}
// Get thread state to check AI mode
Expand Down
12 changes: 12 additions & 0 deletions packages/adapter-signal/CHANGELOG.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,12 @@
# @chat-adapter/signal

## 4.15.0

### Minor Changes

- Add a new Signal adapter package with webhook handling, message send/edit/delete, reactions, typing indicators, DM support, and cached fetch APIs.

### Patch Changes

- chat@4.15.0
- @chat-adapter/shared@4.15.0
Loading