feat: add WhatsApp Business Cloud API adapter#102
feat: add WhatsApp Business Cloud API adapter#102haydenbleasel merged 50 commits intovercel:mainfrom
Conversation
|
@ghellach is attempting to deploy a commit to the Vercel Team on Vercel. A member of the Team first needs to authorize it. |
There was a problem hiding this comment.
Pull request overview
Adds a new @chat-adapter/whatsapp package that integrates the Chat SDK with the WhatsApp Business Cloud API (Meta Graph API), plus the necessary monorepo wiring (tests, env vars, docs).
Changes:
- Introduces
packages/adapter-whatsappwith adapter implementation, card rendering, markdown conversion, and a dedicated test suite. - Wires the new package into the workspace tooling (Vitest workspace, Turbo env passthrough, lockfile).
- Updates docs/README/CLAUDE.md to include WhatsApp as a supported platform and document its package.
Reviewed changes
Copilot reviewed 17 out of 18 changed files in this pull request and generated 7 comments.
Show a summary per file
| File | Description |
|---|---|
| vitest.workspace.ts | Adds the WhatsApp adapter package to the Vitest workspace. |
| turbo.json | Adds WhatsApp-related environment variables to Turbo’s global env passthrough list. |
| pnpm-lock.yaml | Adds the new package importer and updates dependency snapshots accordingly. |
| packages/adapter-whatsapp/vitest.config.ts | Defines Vitest config/coverage settings for the new adapter package. |
| packages/adapter-whatsapp/tsup.config.ts | Adds build configuration for bundling the adapter. |
| packages/adapter-whatsapp/tsconfig.json | Adds TypeScript configuration for the new package. |
| packages/adapter-whatsapp/src/types.ts | Introduces WhatsApp webhook/media/type definitions used by the adapter. |
| packages/adapter-whatsapp/src/markdown.ts | Implements WhatsApp-specific markdown conversion via AST conversion. |
| packages/adapter-whatsapp/src/markdown.test.ts | Tests markdown conversion behavior. |
| packages/adapter-whatsapp/src/index.ts | Implements the WhatsApp adapter: webhook handling, send/reaction/typing/read, thread ID encode/decode, media download, message parsing. |
| packages/adapter-whatsapp/src/index.test.ts | Tests thread ID logic, parsing behavior, attachments, and webhook verification challenge. |
| packages/adapter-whatsapp/src/cards.ts | Converts Card elements into WhatsApp interactive payloads or text fallback. |
| packages/adapter-whatsapp/src/cards.test.ts | Tests card conversion and fallback logic. |
| packages/adapter-whatsapp/package.json | Adds the new package manifest, scripts, and dependencies. |
| apps/docs/content/docs/index.mdx | Adds WhatsApp to the platform list and package table in docs landing page. |
| apps/docs/content/docs/adapters/index.mdx | Adds WhatsApp to the adapters overview matrices and adapter list. |
| README.md | Adds WhatsApp to the supported platforms and adapter list. |
| CLAUDE.md | Adds WhatsApp adapter package and env vars to repo documentation. |
Files not reviewed (1)
- pnpm-lock.yaml: Language not supported
💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.
|
Nice one @ghellach - jumping in to assist 👍 |
|
The latest updates on your projects. Learn more about Vercel for GitHub.
|
6bc498b to
e92f798
Compare
|
I got this to work on our sample app and made a few fixes:
I'm not sure whether triggering onMention for DMs makes sense. Instead we should trigger a DM |
|
Hey @cramforce @ghellach! 👋 Addressed the DM vs mention concern — pushed a commit that adds a new What changed:
The routing priority is: |
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>
…r 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>
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>
…dicator - 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>
channel.messages yields newest first but AI expects chronological order. Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
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>
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>
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>
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>
Also use ━━━ for thematic breaks instead of --- to avoid remark escaping. Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
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>
|
Alright. Fixed markdown conversion and added a reply test |
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
|
Hey @haydenbleasel @cramforce thanks for jumping in and improving this one! CI was failing because the lockfile was out of date. Merged main in to fix that. Also fixed a minor TS error in the replay test. |
Summary
@chat-adapter/whatsapppackage implementing the WhatsApp Business Cloud API adapter using the Meta Graph API (v21.0, configurable viaapiVersionoption)downloadMedia()with lazyfetchData()on attachmentsisMention: true) for correct SDK routingDetails
Capabilities
Media support
Inbound media messages (images, documents, audio, video, voice, stickers) are exposed as
Attachmentobjects on theMessagewith:type—"image","file","audio","video"mimeType— from WhatsApp webhook payloadfetchData()— lazy download via two-step Graph API (get URL, then fetch binary)Location messages include a Google Maps URL and structured text with name/address/coordinates. Latitude and longitude are validated with
Number.isFinite()before URL construction.Thread ID format
whatsapp:{phoneNumberId}:{userWaId}Environment variables
WHATSAPP_ACCESS_TOKEN— Meta Graph API access tokenWHATSAPP_APP_SECRET— App secret for webhook signature verificationWHATSAPP_PHONE_NUMBER_ID— Phone number ID for sending messagesWHATSAPP_VERIFY_TOKEN— Token for webhook URL verificationTest plan
*bold*/~strike~↔ standard markdown, escaped chars)pnpm typecheckpasses across all packagespnpm check(lint/format) passespnpm knip(unused exports/deps) passespnpm testpasses (78 WhatsApp adapter tests + all existing tests)