Commit Graph

14 Commits

Author SHA1 Message Date
OSS Polar Bear 75cadbf9aa feat(twilio): add RCS support for interactive inbound and rich outbound (#590)
Extend the Twilio adapter with RCS webhook parsing, Content API
integration, and card-to-template mapping with SMS fallback.

---------

Co-authored-by: Ben Sabic <bensabic@users.noreply.github.com>
2026-08-28 19:57:44 +10:00
Santiago Medina caa63253c5 feat(x): add XChat encrypted messaging support (#745)
## summary

new `@chat-adapter/xchat` adapter for XChat, X's encrypted messaging.
write bot logic once and hold encrypted 1:1 and group conversations like
the other Chat SDK adapters — all crypto handled inside the adapter via
`@xdevplatform/chat-xdk` (wasm), all REST via the typed
`@xdevplatform/xdk` client.

## background: chat-xdk


[`@xdevplatform/chat-xdk`](https://www.npmjs.com/package/@xdevplatform/chat-xdk)
is the official XChat cryptography SDK — a Rust core compiled to
WebAssembly that implements the XChat encryption protocol. it handles
per-conversation symmetric keys and key exchange, message
encryption/decryption, event signing and signature verification, and
encrypted media (secretstream). the bot's private keys live in a
PIN-protected [Juicebox](https://juicebox.xyz) store (secret-shared
across independent realms), so no key material sits in env vars or on
disk — the adapter unlocks with a PIN at startup. this adapter is the
glue: chat-xdk produces and consumes the encrypted envelopes, the typed
`@xdevplatform/xdk` client moves them over the X API, and everything is
normalized to the Chat SDK's `Thread`/`Message` model.

what it supports:
- encrypted send/receive in DMs and groups (webhook push + polling),
signature verification on by default
- mention detection from structured mention entities, swipe-replies to
the bot, and a plain-text `@handle` fallback; group replies go out as
quoted replies with TTL propagated
- `openDM(userId)`: starts (or reuses) an encrypted 1:1 —
cached/history-recovered conversation key, else a full key exchange so
the bot can message first
- media both ways: inbound attachments with lazy download+decrypt,
outbound encrypted (secretstream) via the 3-step upload flow
- edit and delete of the bot's own messages: edits are encrypted events
targeting the original's sequence id; deletes are locally signed
delete-for-all actions recipients verify
- reactions in and out, typing keep-alive while handlers run,
configurable group welcome message
- read receipts sent per delivered inbound message (`sendReadReceipts`,
default on)
- cards by degradation: text + tappable entities, link buttons as
`label: url` lines, primary link as a URL preview attachment with
optional encrypted banner

key design decisions:
- mdast stays the canonical format; markdown passes through as raw text
(XChat clients render plain text — no markdown), with URLs and @mentions
made tappable via entity spans and tables degraded to ASCII code blocks
- thread ids are `xchat:{conversationId}` (groups `g…`, 1:1s the sorted
participant pair)
- the first edit of a fresh message is age-gated (`editSafetyDelayMs`,
default 5000ms): receiving clients park an edit whose original hasn't
arrived, leaving the message permanently invisible — the gate prevents
that race
- undecryptable or unverified events are dropped, never delivered as
empty messages
- no core changes: the adapter implements the standard `Adapter`
interface only

also includes the `chat/adapters` catalog entry, docs page (with OG
image), `adapters.json` registry entry, and `create-chat-sdk` scaffold
spec, modeled on the `x` adapter's registration.

<details><summary>usage</summary>

```bash
XCHAT_BOT_TOKEN=...    # OAuth2 user access token (identity resolved from GET /2/users/me)
XCHAT_PIN=...          # Juicebox PIN that unlocks the bot's keys
X_CONSUMER_SECRET=...  # optional: verifies webhook signatures
```

```typescript
import { Chat } from "chat";
import { createXchatAdapter } from "@chat-adapter/xchat";
import { createMemoryState } from "@chat-adapter/state-memory";

const bot = new Chat({
  userName: "mybot",
  adapters: { xchat: createXchatAdapter() }, // credentials from env
  state: createMemoryState(),
});

// DMs always
bot.onDirectMessage(async (thread, message) => {
  await thread.post(`You said: ${message.text}`);
});

// group chats when the bot is @mentioned
bot.onNewMention(async (thread, message) => {
  await thread.post("You rang?");
});

// wire the webhook (e.g. a Next.js route)
export async function POST(request: Request) {
  return bot.webhooks.xchat(request);
}
```

</details>

testing: 109 unit tests, including real-wasm-crypto round trips against
vendored fixture vectors (decrypt + signature verification, webhook
delivery, read receipts, edit age-gating, signed deletes). verified live
against production XChat: DMs, group mentions, media, reactions, edits,
deletes, openDM, cards.

note on the lockfile: `@xdevplatform/xdk@0.6.6` was published <48h ago,
so it was resolved with a one-shot `--config.minimumReleaseAge=0`
override; the locked integrity hash was verified against the npm
registry. the repo policy file is untouched.

---------

Co-authored-by: dancer <josh@afterima.ge>
2026-07-31 23:52:18 +01:00
josh ef2542c5fd feat(x): add X (Twitter) adapter (#682)
## summary

new `@chat-adapter/x` adapter for X (Twitter), built on the X API v2 and
the X Activity API. write bot logic once and reply to mentions, hold DM
conversations, post from the account, and like posts, like the other
Chat SDK adapters

what it supports:
- reply to public mentions (`post.mention.create`) and top-level posts
via `channel.post`
- send and receive direct messages (`dm.received` / `dm.sent`)
- edit and delete owned posts, delete own DM events
- likes as the only reaction (`emoji.heart` or `"like"`)
- buffered streaming: accumulates an LLM stream and posts once instead
of post+edit churn on a public timeline
- OAuth 2.0 user context with managed token refresh (rotating refresh
token persisted in the state adapter, optional AES-256-GCM encryption)
- webhook CRC and `x-twitter-webhooks-signature` verification

key design decisions:
- DMs are threaded by the other participant's user id (`x:dm:{userId}`)
because X DM webhooks carry no conversation id, only participants
- OAuth 2.0 only at runtime: DM send and read are verified to work on
OAuth 2.0 user tokens, so no OAuth 1.0a in the adapter (subscription and
webhook setup is one-time and handled in the X developer console)
- parsers were written against real captured payloads: mentions use the
v2 shape (author hydrated in `includes.users`), DMs use the legacy
Account Activity shape (`direct_message_events`,
`message_create.message_data`, a `users` map, and no conversation id)

also includes the `chat/adapters` catalog entry, docs page, CLI scaffold
spec, and `sample-messages.md` with real captured payloads

<details><summary>usage</summary>

```typescript
import { Chat } from "chat";
import { createXAdapter } from "@chat-adapter/x";

const bot = new Chat({
  userName: "mybot",
  adapters: { x: createXAdapter() },
});

bot.onNewMention(async (thread, message) => {
  await thread.post(`hi @${message.author.userName}!`);
});

bot.onDirectMessage(async (thread) => {
  await thread.post("hello from X");
});
```
</details>

## test plan

- adapter unit tests pass against the real captured payload shapes, with
regression tests for author-from-`includes` (mentions) and the legacy
`direct_message_events` shape (DMs)
- real captured `post.mention.create` and `dm.received` payloads
verified end-to-end through `handleWebhook`: signature verification,
routing, author resolution, and participant threading, plus
bad-signature rejection returns 401
- every write and read path fired live against the X API through the
adapter: top-level post, reply to a mention, like and unlike, edit,
delete, DM send, DM read, DM delete
- OAuth 2.0 managed token refresh exercised live (access and refresh
token rotation)

---------

Signed-off-by: dancer <josh@afterima.ge>
2026-07-07 23:07:36 +01:00
josh 25ebc3b925 feat(twilio): add sms, mms, and voice helpers (#558)
## summary

adds a first-class Twilio adapter for SMS and MMS bots, plus low-level
voice helpers for custom Twilio voice routes

this includes webhook parsing and signature verification, outbound
Messages API helpers, phone-number and Messaging Service sending,
inbound MMS attachments with authenticated `fetchData`, plain text card
fallback rendering, markdown conversion, and runtime-light `api`,
`webhook`, `voice`, and `format` subpaths

the adapter intentionally avoids the `twilio` npm runtime dependency so
apps can use the low-level helpers without pulling in the full SDK
2026-05-27 15:39:23 -07:00
Vishal Yathish 68025ca965 [messenger] add messenger (meta) platform adapter to chat sdk (#461)
* [messenger] add messenger (meta) platform adapter to chat sdk

- Webhook handling with HMAC-SHA256 signature verification
- Generic and Button template support for cards
- Postback, reaction, delivery/read confirmation handling
- Message caching for fetchMessages (Messenger has no history API)
- Replay tests and ~98% code coverage

Co-authored-by: Dimitar K. Nikolov <mitkodkn@users.noreply.github.com>
Co-authored-by: Ben Sabic <27636870+bensabic@users.noreply.github.com>
Co-authored-by: Cursor <cursoragent@cursor.com>

* Fix: The `@chat-adapter/messenger` package version is `4.15.0` while all other packages in the Changesets fixed version group are at `4.27.0`, breaking the fixed versioning contract.

This commit fixes the issue reported at packages/adapter-messenger/package.json:3

**Bug explanation:**

The repository uses Changesets with a `"fixed"` configuration: `[["chat", "@chat-adapter/*"]]`. This means all packages matching these patterns must always share the same version number. Every package in the group (`chat`, `@chat-adapter/discord`, `@chat-adapter/gchat`, `@chat-adapter/github`, `@chat-adapter/linear`, `@chat-adapter/shared`, `@chat-adapter/slack`, `@chat-adapter/teams`, `@chat-adapter/telegram`, `@chat-adapter/web`, `@chat-adapter/whatsapp`, and the state packages) is at version `4.27.0`, except `@chat-adapter/messenger` which is at `4.15.0`.

This is likely because the messenger adapter was newly added to the monorepo (copied from a template or created fresh) and its version was never aligned with the rest of the fixed group. This mismatch will cause problems with the Changesets release workflow — when Changesets tries to bump versions for the fixed group, it may produce inconsistent or errored releases because one package is 12 minor versions behind the others.

**Fix explanation:**

Changed `"version": "4.15.0"` to `"version": "4.27.0"` in `packages/adapter-messenger/package.json` to align it with all other packages in the fixed version group.

Co-authored-by: Vercel <vercel[bot]@users.noreply.github.com>
Co-authored-by: visyat <vishal.yathish@gmail.com>

* Fix: Messenger adapter env var guard only checks `FACEBOOK_APP_SECRET` but `createMessengerAdapter` requires all three env vars, causing a `ValidationError` crash at Next.js build time when only `FACEBOOK_APP_SECRET` is set.

This commit fixes the issue reported at examples/nextjs-chat/src/lib/adapters.ts:154

**Bug Analysis:**

The build failure is confirmed in the Vercel build log with:
```
Error [ValidationError]: pageAccessToken is required. Set FACEBOOK_PAGE_ACCESS_TOKEN or provide it in config.
```

The root cause is in `examples/nextjs-chat/src/lib/adapters.ts` at line ~154. The messenger adapter guard only checks for `FACEBOOK_APP_SECRET`:
```typescript
if (process.env.FACEBOOK_APP_SECRET) {
```

However, `createMessengerAdapter` (in `packages/adapter-messenger/src/index.ts`) validates and throws `ValidationError` for each of three required env vars: `FACEBOOK_APP_SECRET`, `FACEBOOK_PAGE_ACCESS_TOKEN`, and `FACEBOOK_VERIFY_TOKEN`. When only `FACEBOOK_APP_SECRET` is set in the Vercel project environment, the guard passes, `createMessengerAdapter` is called, and it throws a `ValidationError` for the missing `FACEBOOK_PAGE_ACCESS_TOKEN`. Since this code runs at module evaluation time during the Next.js build's "Collecting page data" phase, the uncaught error crashes the entire build.

This is inconsistent with other adapters in the same file. For example, the WhatsApp adapter checks both `WHATSAPP_ACCESS_TOKEN` and `WHATSAPP_PHONE_NUMBER_ID`, and the gchat/github/linear/whatsapp adapters all wrap creation in try-catch blocks.

**Fix:**

1. Updated the env var guard to check all three required environment variables (`FACEBOOK_APP_SECRET`, `FACEBOOK_PAGE_ACCESS_TOKEN`, and `FACEBOOK_VERIFY_TOKEN`) before attempting to create the adapter.
2. Wrapped the `createMessengerAdapter` call in a try-catch block (matching the pattern used by gchat, github, linear, and whatsapp adapters) so that any unexpected validation errors are caught and logged as warnings instead of crashing the build.

Co-authored-by: Vercel <vercel[bot]@users.noreply.github.com>
Co-authored-by: visyat <vishal.yathish@gmail.com>

---------

Co-authored-by: Dimitar K. Nikolov <mitkodkn@users.noreply.github.com>
Co-authored-by: Ben Sabic <27636870+bensabic@users.noreply.github.com>
Co-authored-by: Cursor <cursoragent@cursor.com>
Co-authored-by: vercel[bot] <35613825+vercel[bot]@users.noreply.github.com>
Co-authored-by: Vercel <vercel[bot]@users.noreply.github.com>
2026-05-08 17:25:54 +10:00
Ben Sabic 051245c4b0 feat(docs): add Resources page powered by Edge Config (#393)
* feat(docs): add Resources page powered by Edge Config

Add a /resources page that displays guides and templates in a
3-column card grid. Data is fetched from Vercel Edge Config in
production and from a local JSON file in development. Includes
CollectionPage JSON-LD markup and revalidates daily.

* fix(docs): handle Edge Config unavailability on resources page

* feat(docs): add keywords metadata to resources page

* feat(docs): add twitter card metadata to resources page

---------

Co-authored-by: Ben Sabic <bensabic@users.noreply.github.com>
2026-04-16 22:42:04 +10:00
Achraf Ghellach 60f5d8e19f feat: add WhatsApp Business Cloud API adapter (#102)
* feat: add WhatsApp Business Cloud API adapter

Add @chat-adapter/whatsapp with support for sending/receiving messages,
reactions, interactive reply buttons, typing indicators, and webhook
verification via the Meta Graph API. Includes full test suite,
documentation updates, and workspace/turbo configuration.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>

* feat: add media download, attachments, and location support to WhatsApp adapter

- Add downloadMedia() public method for fetching images, documents,
  audio, video, and stickers via the Graph API (two-step: URL then binary)
- Populate message attachments with lazy fetchData() for all media types
- Add location support with Google Maps URL and structured text
- Add audio, video, sticker, and location fields to WhatsAppInboundMessage
- Set isMention: true on all messages (WhatsApp DMs are always direct)
- Update parseMessage to include attachments and isMention
- Add 10 new tests covering all media types, locations, and isMention
- Update docs feature matrix to reflect media receive support

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>

* fix: address PR review feedback for WhatsApp adapter

- Validate Graph API response before accessing messages[0].id in
  sendTextMessage and sendInteractiveMessage
- Escape backticks and backslashes in escapeWhatsApp()
- Apply escapeWhatsApp() to renderText() content in all style branches
- Use webhook phoneNumberId in buildMessage() instead of this.phoneNumberId
- Encode proper threadId in parseMessage() instead of empty string
- Strict decodeThreadId() validation (exactly 2 segments after prefix)
- Add tests for extra segments in decodeThreadId and threadId in parseMessage

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>

* Migrate improvements from #179

Bring over several enhancements from chitru's WhatsApp adapter PR (#179):

- Voice message support (separate from audio)
- Legacy button response handling (template quick replies)
- Callback data encoding/decoding for interactive reply round-trips
- Message truncation at WhatsApp's 4096 char limit
- Example app integration (adapters, webhook route, package.json)
- GET webhook forwarding for WhatsApp verification challenges
- Package README and changeset
- Tests for all new functionality (68 total)

Co-Authored-By: Chitru Shrestha <chitra.shrestha@akuru.com.au>
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>

* fix(whatsapp): add error handling for inbound message processing

Wrap handleInboundMessage calls in try/catch to log errors if
synchronous processing fails (e.g., thread ID encoding). The async
processing already has its own error handling in Chat.processMessage.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>

* fix(whatsapp): prevent markdown regex from matching across newlines

Use [^\n*] and [^\n~] in fromWhatsAppFormat regex to prevent bold/strike
spans from merging across line boundaries. Adds a regression test.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>

* fix(whatsapp): use WhatsAppInteractiveMessage type instead of object

Replace the untyped `object` parameter in sendInteractiveMessage with
the proper WhatsAppInteractiveMessage type for full type safety.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>

* fix(whatsapp): hoist emoji mapping to module-level constant

Move the emoji name-to-unicode mapping out of resolveEmoji() so it is
not re-allocated on every call.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>

* fix(whatsapp): remove duplicate JSDoc comment in types

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>

* fix(example): add startTyping to WhatsApp recording methods

The adapter supports typing indicators but the method was missing from
the recording proxy list.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>

* fix(whatsapp): fix formatting and add package to readme test allowlist

Fix line-length formatting in markdown.ts regex and add
@chat-adapter/whatsapp to the valid packages list in readme tests.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>

* fix(whatsapp): use defaultEmojiResolver instead of custom emoji map

Replace the hand-rolled EMOJI_MAP with the shared defaultEmojiResolver
from the chat SDK. WhatsApp uses unicode emoji like GChat, so toGChat()
provides the correct mapping with broader coverage.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>

* fix(whatsapp): make Graph API version configurable

Add apiVersion option to WhatsAppAdapterConfig (defaults to v21.0)
so users can upgrade without waiting for a package release.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>

* fix(whatsapp): validate lat/lng before constructing Google Maps URL

Coerce and validate latitude/longitude with Number.isFinite() to
prevent unexpected URL construction from malformed webhook payloads.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>

* fix(whatsapp): throw on editMessage instead of silently sending new message

Callers expecting an edit would get duplicate messages with the silent
fallback. Throwing makes the unsupported operation explicit.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>

* fix(whatsapp): document regex asymmetry between toWhatsApp and fromWhatsApp

Explain why toWhatsAppFormat doesn't need newline guards like
fromWhatsAppFormat does — the standard markdown parser output
never produces spans crossing line boundaries.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>

* fix(whatsapp): document callback data passthrough behavior

Add comments explaining that non-prefixed and malformed callback data
is intentionally passed through for legacy/external button IDs.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>

* fix(whatsapp): add editMessage and deleteMessage to recording methods

Include all adapter methods in the recording list for complete
debugging traces, even for unsupported operations.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>

* fix(whatsapp): preserve escaped formatting chars in toWhatsAppFormat

Escaped asterisks and tildes in standard markdown (e.g. \* and \~) are
now preserved through the conversion pipeline so WhatsApp renders them
as literal characters instead of misinterpreting them as formatting.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>

* fix(whatsapp): split long messages instead of truncating

Replace silent truncation at 4096 chars with message splitting that
breaks on paragraph (\n\n) then line (\n) boundaries, sending multiple
messages so no content is lost. Adds 8 tests for the splitting logic.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>

* fix(whatsapp): align editMessage/deleteMessage behavior and docs

- Fix README: editMessage/deleteMessage both throw, not fallback/no-op
- Fix editMessage JSDoc to reflect it throws
- Make deleteMessage throw instead of silently warning (consistent with editMessage)
- Bump @types/node to ^25.3.2 to match monorepo
- Add sample-messages.md with webhook payload examples

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>

* docs(whatsapp): add adapter documentation page

Add whatsapp.mdx covering installation, usage, Meta app setup,
webhook config, interactive messages, media attachments, 24-hour
messaging window, configuration, features, and troubleshooting.
Also add WhatsApp to the adapters navigation in meta.json.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>

* fix: add whatsapp adapter debug logging and try/catch

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>

* fix(whatsapp): convert emoji placeholders in outgoing messages

WhatsApp adapter was sending raw {{emoji:wave}} placeholders instead of
Unicode emoji. Apply convertEmojiPlaceholders on all outgoing paths:
text messages, card fallback text, and interactive message fields.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>

* Fix button rendering and streaming (needs to buffer)

* fix(example): handle editMessage failure on WhatsApp

WhatsApp Cloud API doesn't support message editing. Catch the error
in the demo "processing" animation and send a follow-up instead.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>

* feat(chat): add onDirectMessage handler, stop treating DMs as mentions

DMs now route to dedicated onDirectMessage handlers instead of being
forced through onNewMention. If no DM handlers registered, DMs fall
through to onNewMention for backward compat. Adapters no longer set
isMention=true for DMs — the Chat SDK handles routing via adapter.isDM().

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>

* feat(chat): always route DMs to onDirectMessage regardless of subscription

Previously, onDirectMessage only fired for unsubscribed DM threads.
Subscribed DMs were routed to onSubscribedMessage, which was confusing
on non-threaded platforms (WhatsApp, Telegram) where all DMs share one
threadId — after the first message, onDirectMessage never fired again.

Now, DMs always route to onDirectMessage first, and onSubscribedMessage
only handles non-DM subscribed threads. Backward compat is preserved:
if no onDirectMessage handlers are registered, DMs fall through as
mentions.

The example bot is simplified accordingly — onDirectMessage now fetches
conversation history via fetchMessages each time instead of relying on
subscribe() and stored state.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>

* feat(chat): pass channel as third argument to DirectMessageHandler

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>

* fix(example): reply to channel instead of thread in DM handler

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>

* fix(example): use thread instead of channel for DM operations

Channel ID is only two parts (whatsapp:{phoneNumberId}) which isn't a
valid conversation target on WhatsApp. The thread ID includes the user
phone and is required for startTyping/post/fetchMessages.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>

* Revert "fix(example): use thread instead of channel for DM operations"

This reverts commit f4801d7015.

* fix(adapters): return valid thread IDs from channelIdFromThreadId

WhatsApp's channelIdFromThreadId was stripping the user WA ID, producing
an invalid ID that caused ValidationError on channel operations like
startTyping(). Since every WhatsApp conversation is a 1:1 DM, channel
and thread are identical.

Telegram's channelIdFromThreadId was returning a raw chatId without the
telegram: prefix, which is not a valid thread ID for adapter operations.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>

* fix(chat): normalize fullStream in Channel.post() to extract text deltas

Channel.post() was coercing AI SDK fullStream objects to strings via +=,
producing "[object Object]" output. Now uses fromFullStream() to extract
text-delta events, matching how Thread.post() already handles streams.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>

* fix(example): use thread.allMessages for DM history instead of adapter directly

The DM handler was calling channel.adapter.fetchMessages() which always
returns empty on WhatsApp (no native history API). Now uses
thread.allMessages which falls back to the persisted message history
cache, giving the AI conversation context.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>

* feat(chat): add message history support to Channel for DM platforms

Channel now falls back to the persisted message history cache when the
adapter lacks native message fetch (e.g. WhatsApp, Telegram). Incoming
messages are persisted under both thread and channel IDs. Outgoing
messages from channel.post() are also persisted.

The example DM handler now uses channel.messages instead of calling the
adapter directly.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>

* fix(whatsapp): improve markdown rendering and remove broken typing indicator

- Convert headings to bold text, thematic breaks to text separators,
  and tables to code blocks (WhatsApp doesn't support these)
- Convert standard italic (*text*) to WhatsApp italic (_text_) since
  WhatsApp uses *text* for bold
- Make startTyping a no-op (Cloud API doesn't support typing indicators)
- Update channelIdFromThreadId test for channel===thread change

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>

* fix(example): reverse channel.messages to chronological order for AI

channel.messages yields newest first but AI expects chronological order.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>

* fix(chat): auto-sort messages chronologically in toAiMessages

toAiMessages now sorts by dateSent (oldest first) so callers don't need
to worry about iteration order from channel.messages or thread.messages.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>

* fix(chat): pass accumulated stream text as markdown in Channel.post()

Stream text was posted as a plain string, bypassing the adapter's format
converter. Now wraps it as { markdown: accumulated } so headings, bold,
italic etc. are properly converted for each platform.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>

* fix(whatsapp): use stringifier options for emphasis and bullets

Use emphasis: '_' and bullet: '-' options in stringifyMarkdown so the
only * in output is **strong**, avoiding conflicts between list bullets
and italic markers. Simplifies toWhatsAppFormat to only convert
**bold** -> *bold* and ~~strike~~ -> ~strike~.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>

* fix(whatsapp): flatten bold inside headings to avoid triple asterisks

When AI outputs headings with bold text like `## **Choose React if:**`,
the heading-to-bold conversion created nested strong nodes producing
`***text***`. Now flattens strong children in headings so they merge
into a single bold span.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>

* test(whatsapp): add full toBe assertion for complex markdown conversion

Also use ━━━ for thematic breaks instead of --- to avoid remark escaping.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>

* test: add WhatsApp replay tests from production recordings

Adds WhatsApp DM replay test infrastructure:
- Fixture from real webhook recordings (dm/whatsapp.json)
- WhatsApp test utilities with HMAC-signed request factory and
  Graph API fetch mock (whatsapp-utils.ts)
- 6 replay tests covering DM handling, thread/channel IDs, message
  sending, status update filtering, sequential messages, and
  message history persistence

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>

* fix(whatsapp): fix type narrowing in replay test after merge

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>

* Update WhatsApp logo

* Update adapters.json

* Update logos.tsx

---------

Co-authored-by: Claude Opus 4.6 <noreply@anthropic.com>
Co-authored-by: Hayden Bleasel <hello@haydenbleasel.com>
Co-authored-by: Chitru Shrestha <chitra.shrestha@akuru.com.au>
Co-authored-by: Malte Ubl <malte.ubl@gmail.com>
2026-03-10 15:16:10 -07:00
Hayden Bleasel e970a6939b Upgrade Biome configuration to use Ultracite preset (#81)
* Upgrade Biome to Ultracite

* Remove package commands

* Update biome.jsonc

* Update biome.jsonc

* Initial fixes

* Update biome.jsonc

* Remaining fixes

* Update pnpm-lock.yaml

* Fix commands

* Update knip.json

* Merge Claude files

* Fix skipped test

* Misc fixes
2026-02-20 22:01:10 -08:00
Hayden Bleasel 2c05318ab7 Bake docs into chat package 2026-02-20 20:02:41 -08:00
Malte Ubl b9186d8758 step 2025-12-31 14:24:46 -08:00
Malte Ubl 80949e01b8 teams10 2025-12-22 16:28:34 -08:00
Malte Ubl bf18adc163 OIDC 2025-12-22 12:13:14 -08:00
Malte Ubl b8e1d45000 turbo-env 2025-12-22 09:04:40 -08:00
Malte Ubl 1f401aacd0 initial cook 2025-12-21 20:48:14 -08:00