* [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>
* feat(chat): expose awaitable Promise from processMessage
Return the inner task as Promise<void> instead of void so streaming
adapters can await full handler completion and surface user-handler
rejections at the wire level. waitUntil semantics for existing webhook
adapters are unchanged — the SDK still tracks the work with errors
swallowed (and logged) so platforms don't retry on handler bugs.
Required by @chat-adapter/web, whose response body is the user
handler's stream.
* feat(adapter-web): add @chat-adapter/web package
A new platform adapter that lets a chat-sdk bot serve a browser chat
UI alongside Slack/Teams/Discord/etc. without writing any client-side
glue. Speaks the AI SDK UI message stream protocol, so @ai-sdk/react's
useChat and the ai-elements component library work out of the box.
- `@chat-adapter/web` — server: createWebAdapter({ userName, getUser })
- `@chat-adapter/web/react` — client: useChat() preconfigured with
DefaultChatTransport against /api/chat (override via `api`)
Defaults that matter for v1:
- `isDM: true` — every web message routes through onDirectMessage
- `persistMessageHistory: true` — chat-sdk caches each turn in the
configured state adapter so handlers can read prior context via
thread.messages / channel.messages (no platform history API exists)
- channelId === threadId — web has no separate channel concept; this
prevents cross-conversation bleed when a single user has multiple
useChat sessions
- Native `adapter.stream` implementation pumps text-deltas straight
onto the SSE response — no post+edit fallback
Out of scope for v1: cards/JSX rendering, reactions, modals, file
uploads, edit/delete, multi-tab proactive push.
* feat(example-nextjs-chat): wire up web adapter and add /chat page
- Register the web adapter in lib/adapters.ts with a demo getUser
(single shared identity — replace with NextAuth/Clerk/cookie auth
in production)
- Expose POST /api/chat backed by bot.webhooks.web (using next/after
for waitUntil)
- Add a minimal /chat page using @chat-adapter/web/react's useChat —
same bot.onDirectMessage handler that powers Slack now powers the
browser too
Bumps `ai` to ^6.0.174 to align with @ai-sdk/react@^3 (avoids dual
provider-utils versions in the workspace).
* docs: list @chat-adapter/web in registry
- Add an entry to adapters.json so the package shows up on /adapters
- Add a globe SVG to lib/logos.tsx and wire it into the icon map
- Mention the new adapter in docs/adapters.mdx
* feat(adapter-web): tighten request handling and message construction
- Reject user ids containing ':' with HTTP 400 — the character would
corrupt the thread-id round-trip through decodeThreadId
- Skip emitting text-start/text-end in postMessage when the resolved
text is empty so useChat doesn't render blank assistant bubbles
- Derive the parseMessage author from raw.role so rehydrated assistant
messages report the bot identity instead of "unknown"
- Drop the duplicate handler-error log; chat.processMessage already
logs at ERROR level
- Document the actual persistMessageHistory default (true) and the
state-cache rationale; promote the fetchMessages no-op rationale
into its JSDoc
* test(adapter-web): add direct coverage for stream()
- Aborting request.signal mid-stream short-circuits the iterator and
still writes text-end via the finally block
- Non-text StreamChunks (task_update, plan_update) are dropped without
emitting any delta
- The SentMessage returned from thread.post matches the id used in
text-start / text-end events
* docs(adapter-web): expand README into the full adapter docs page
The docs site renders each adapter's README, so flesh out
@chat-adapter/web to match the depth of @chat-adapter/slack:
authentication boundary, threading semantics, streaming,
persistence, React hook reference, configuration table,
feature matrix, and troubleshooting.
* docs(adapter-web): drop unsupported provider import from streaming example
* fix(adapter-web): validate conversationId for reserved colon character
* fix(example): show error state in web chat demo
* fix(example): add thinking indicator to web chat demo
* feat(example): redesign web chat demo with tailwind
* chore: remove redundant changeset
---------
Co-authored-by: Ben Sabic <bensabic@users.noreply.github.com>
Co-authored-by: dancer <josh@afterima.ge>
* Bump Teams SDK to 2.0.8 and switch to User-Agent header
Co-Authored-By: Claude <noreply@anthropic.com>
* fix(teams): remove unused ts-expect-error after SDK 2.0.8 upgrade
---------
Co-authored-by: dancer <josh@afterima.ge>
* fix(telegram): switch parse_mode from legacy Markdown to MarkdownV2
The Telegram adapter hardcoded `parse_mode: "Markdown"` (legacy) but
rendered messages via the SDK's generic `stringifyMarkdown()`, which
emits standard markdown. Two incompatible dialects glued together:
- Standard markdown uses `**bold**`, Telegram legacy uses `*bold*`
- Legacy Markdown has no escape rules — any message with `.`, `!`,
`(`, `)`, `-`, `_` in unexpected positions was rejected with
`can't parse entities`, which is virtually every LLM-generated
response
- Legacy Markdown is deprecated by Telegram and lacks support for
underline, strikethrough, spoiler, and blockquote
This commit:
- Switches TELEGRAM_MARKDOWN_PARSE_MODE to "MarkdownV2"
- Replaces fromAst() with a proper AST → MarkdownV2 renderer:
- Single `*bold*`, `_italic_`, `~strike~` markers
- Context-aware escaping: 20-char matrix for normal text, only
`` ` `` and `\` inside code blocks, only `)` and `\` inside link
URLs
- Headings rendered as bold (MarkdownV2 has no heading syntax)
- Ordered/unordered lists with escaped dashes and periods
- Blockquotes with per-line `>` prefix
- Tables pre-empted and rendered as ASCII code blocks
- Explicit handlers for reference-style links, images, HTML, and
definitions so nothing is silently dropped
- Routes card fallback text through `fromMarkdown` (not raw escape)
with `boldFormat: "**"` — @chat-adapter/shared's cardToFallbackText
defaults `boldFormat` to "*" (Slack mrkdwn), which would render as
italic on Telegram. Explicit "**" keeps the card title rendered as
real MarkdownV2 bold.
- Fixes resolveParseMode so every message routed through the format
converter (`{markdown}`, `{ast}`, cards, JSX) gets
`parse_mode: "MarkdownV2"`. Previously only `{markdown}` and cards
were covered, so `{ast}` messages shipped without parse_mode and
rendered asterisks literally.
- Documents inbound vs outbound dialects on applyTelegramEntities /
escapeMarkdownInEntity (inbound entities → standard markdown)
versus the new outbound MarkdownV2 renderer, so future
contributors don't confuse the two.
Tests: full 20-char MarkdownV2 escape matrix, context-escape tests
for code blocks and link URLs, nested-formatting tests, edge cases
(empty, whitespace-only, raw HTML), and an end-to-end LLM-output
corpus test that asserts MarkdownV2 validity (no unescaped special
chars outside entities or code blocks). Regression guards added in
index.test.ts for the AST / plain-string / raw parse_mode paths and
for card-title MarkdownV2 bold rendering.
Fixes#226
* feat(examples): add telegram-chat reference bot
Polling-mode Telegram bot that exercises the adapter end-to-end:
MarkdownV2 rendering, interactive cards with inline keyboards,
reactions, file uploads, and streaming edits. Runs with a single
`pnpm --filter example-telegram-chat start`; no webhook, no public
URL, no external API keys.
Menu structure — three categorized sub-menus reached from any DM text:
- Text & Markdown: plain, inline emphasis, code block, links, list+table,
20-char torture string, LLM-style corpus, streaming editMessage loop
- Cards & Actions: interactive approval card (edits in-place on press),
callback_data size probe demonstrating the 64-byte limit, LinkButton
- Media & Reactions: on-demand reaction one-shot (briefly subscribes),
generated 1×1 PNG upload, generated minimal PDF upload
Zero new runtime deps. PNG/PDF are hand-rolled in memory
(lib/png.ts, lib/pdf.ts) rather than pulled from a binary-processing
library. Failure handling is consistent: every demo runner is
try/catch-wrapped and posts an inline ❌ line with the error message.
Excluded from npm release via .changeset/config.json.
* fix(telegram): produce valid MarkdownV2 when truncating long messages
The MarkdownV2 migration widened a latent truncation bug into a reliable
400. The previous truncator sliced at 4096/1024 chars and appended
literal "..." — but in MarkdownV2 `.` is a reserved character, the slice
can leave an orphan trailing `\`, and it can cut through a paired
entity (`*bold*`, `` `code` ``) leaving it unclosed.
Unify the two truncate methods into one `truncateForTelegram(text,
limit, parseMode)` that appends `\.\.\.` for MarkdownV2 and walks back
past unbalanced entity delimiters or orphan backslashes. Plain text
keeps literal `...`. Adds 8 length-limit tests.
Related cleanup:
- Move MarkdownV2 string utilities and Bot API limits to markdown.ts.
- Type renderMarkdownV2 exhaustively on mdast's `Nodes` union with a
`never` assertion so new node kinds fail the build. Replaces the
hand-rolled `AstNode` interface. Adds explicit cases for table /
tableRow / tableCell (throw — preprocessed by fromAst),
footnoteDefinition, footnoteReference, yaml.
- Introduce `TelegramParseMode = "MarkdownV2" | "plain"` replacing
`string | undefined`. `toBotApiParseMode` handles the wire mapping.
- Re-export `Nodes` from the chat package; re-export
`TelegramReactionType` from the adapter entry.
* feat(examples): add length-limit demos to telegram-chat reference bot
Three new menu entries exercise the MarkdownV2 truncation path that the
prior commit fixed:
- Long (5000 plain) — basic truncation, verifies escaped `\.\.\.` ellipsis
- Long (bold crosses 4096) — entity-balancing heuristic for unclosed `*`
- Long (code crosses 4096) — entity-balancing heuristic for unclosed `` ` ``
Each button posts a message whose rendered length exceeds Telegram's
4096-char limit and would have produced `can't parse entities` 400s
against the previous truncator. Serves as an interactive smoke test
alongside the unit tests in packages/adapter-telegram.
* test(telegram): add unit tests for truncation helpers and MarkdownV2 boundary trimming
* docs(telegram): update README to reflect MarkdownV2 parse mode
* chore: unexport trimToMarkdownV2SafeBoundary to fix knip
---------
Co-authored-by: dancer <josh@afterima.ge>
* Add slack/socket mode dependency
* Update config types and SlackAdapter class
* Add socket mode methods, extract interactive dispatch
* Update createSlackAdapter factory function
* Write tests for socket mode
* Create slack-socket-mode.md
* Run fix
* Fix polynomial regex issues
* Fix: Floating promises in `routeSocketEvent` for slash commands and interactive payloads can cause unhandled promise rejections that crash the Node.js process.
This commit fixes the issue reported at packages/adapter-slack/src/index.ts:1152
**Bug Analysis:**
In `routeSocketEvent` (line 1150), which is a synchronous `void` method, two async operations produce floating promises:
1. `this.handleSlashCommand(params)` (line 1165) - `handleSlashCommand` is `async` and always returns a `Promise<Response>`. It calls `await this.lookupUser(userId)` which internally calls `await this.chat.getState().get()` (before the try/catch around the API call), and `this.chat.processSlashCommand()`. Any of these could throw.
2. `this.dispatchInteractivePayload(payload)` (line 1172) - Returns `Response | Promise<Response>`. When the payload type is `view_submission`, it delegates to `async handleViewSubmission()`, which calls `await this.chat.processModalSubmit()` and accesses `payload.view.state.values` (which could throw on malformed payloads).
Since `routeSocketEvent` is synchronous (`void` return type) and called from a sync context within the socket mode event handler (after `await ack()` has already completed), these returned promises are fire-and-forget. If any reject, it triggers an unhandled promise rejection, which in Node.js 15+ terminates the process by default.
In contrast, in the webhook code path (`handleWebhook`), these same methods are always `return`-ed from async functions, so their promises are properly chained to the caller.
**Fix:**
Added `.catch()` handlers to both floating promises:
1. For `handleSlashCommand`: Added `.catch()` that logs the error via `this.logger.error`.
2. For `dispatchInteractivePayload`: Since it returns `Response | Promise<Response>` (only a Promise for `view_submission`), used `instanceof Promise` to conditionally attach a `.catch()` handler only when the result is a Promise.
This approach was chosen over making `routeSocketEvent` async because: (a) it doesn't change the method signature, (b) the caller doesn't need to await it (the ack has already been sent), and (c) errors are logged rather than silently swallowed.
Co-authored-by: Vercel <vercel[bot]@users.noreply.github.com>
Co-authored-by: haydenbleasel <hello@haydenbleasel.com>
* Add socket mode forwarding support to Slack adapter
- Export SlackForwardedSocketEvent type
- Add x-slack-socket-token check at top of handleWebhook() for forwarded events
- Update routeSocketEvent() to accept WebhookOptions and use waitUntil
- Add startSocketModeListener(), runSocketModeListener(), forwardSocketEvent()
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
* Add tests for socket mode forwarding
- Forwarded event accepted/rejected based on appToken
- Bypasses signature verification for forwarded events
- Options passthrough to handlers
- startSocketModeListener returns 200/500 appropriately
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
* Add socket mode cron route and vercel config
- New /api/slack/socket-mode route using createPersistentListener
- Mirrors Discord gateway pattern (CRON_SECRET auth, Redis coordination)
- Cron runs every 9 min, listener duration 10 min
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
* Fix signingSecret defaulting to empty string in socket mode
Make signingSecret optional (string | undefined) instead of falling
back to "". verifySignature now returns false when no secret is
configured, preventing HMAC with an empty key from silently passing.
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
* Wrap event_callback in try-catch in routeSocketEvent
Sync errors from processEventPayload were silently dropped in
socket mode. Wrap with try-catch for parity with slash_commands
and interactive cases.
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
* Use dedicated socketForwardingSecret for forwarding auth
Stop using the Slack app-level token (xapp-...) as the bearer token
for HTTP forwarding. Adds socketForwardingSecret config option
(auto-detected from SLACK_SOCKET_FORWARDING_SECRET) with fallback
to appToken for backwards compatibility.
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
* Replace double cast with type guard for socket event body
Validate body.event exists and construct a properly typed
SlackWebhookPayload instead of using `as unknown as`.
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
* Internalize SlackForwardedSocketEvent type
Remove export — only used internally by the forwarding mechanism.
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
* Fix formatting in socketForwardingSecret check
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
* Add socket mode documentation to Slack adapter README
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
* fix(slack): use SDK envelope type for socket mode event routing
* fix(slack): pass interactive response through ack in socket mode
* feat(chat): add clear modal response action to close entire view stack
* chore: update changeset for clear modal action
---------
Co-authored-by: Vercel <vercel[bot]@users.noreply.github.com>
Co-authored-by: Claude Opus 4.6 <noreply@anthropic.com>
Co-authored-by: dancer <josh@afterima.ge>
* 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>
* feat(teams): add dialog (task module) support
Teams dialogs require modal content to be returned inline in the HTTP
response when a task/fetch invoke fires. This adds:
- `actionType: "modal"` on buttons to emit msteams task/fetch hint
- `onOpenModal` hook on WebhookOptions for inline modal interception
- dialog.open/dialog.submit handlers in Teams adapter with Promise.race
- Modal-to-AdaptiveCard converter (modals.ts)
- Bridge adapter sends empty body (not "{}") for dialog close responses
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
* refactor(teams): use @microsoft/teams.cards builders for Adaptive Cards
Replace hand-rolled plain JSON objects and local type definitions with
typed builder classes from @microsoft/teams.cards. This gives compile-time
type safety and eliminates the local AdaptiveCard/AdaptiveCardElement/
AdaptiveCardAction interfaces.
Also fix ephemeral modal button missing actionType="modal", which
prevented the dialog from opening on Teams.
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
* fix(teams): preserve contextId in modal update/push responses
Pass the original contextId through to re-rendered modals so subsequent
submissions can still retrieve the stored thread/message/channel context.
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
* refactor: address review feedback across dialog support
- Clean up timeout timer in handleDialogOpen to prevent resource leak
- Also race on actionPromise so errors surface instead of silently timing out
- Extract buildContinueResponse helper to deduplicate update/push cases
- Use typed TextInputOptions/ChoiceSetInputOptions instead of Record<string, unknown>
- Make processSlashCommand options parameter explicit (WebhookOptions | undefined)
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
* fix(chat): await storeModalContext before opening modal
The state write was fire-and-forget, so a fast dialog.submit could
arrive before the context was persisted, causing retrieveModalContext
to return empty. Also adds changeset for the new public API surface.
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
* fix(chat): add missing disabled prop to ButtonProps JSX interface
The ButtonElement and ButtonOptions already supported disabled, but the
JSX ButtonProps interface was missing it, causing <Button disabled> to
silently drop the prop.
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
* fix: address PR review feedback for dialog support
- Add unit tests for modals.ts (16 tests) and modal button actionType in cards.test.ts
- Make dialog open timeout configurable via dialogOpenTimeoutMs in TeamsAdapterConfig
- Fix ModalSubmitHandler type to accept Promise<void> returns, remove @ts-expect-error from example
- Delete stored modal context after retrieval to prevent state adapter leaks
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
* fix: lint fixes and add X-User-Agent header to Teams adapter
- Fix import ordering, formatting, and non-null assertions in modals.test.ts
- Sort interface members in TeamsAdapterConfig
- Add X-User-Agent: Vercel.ChatSDK header to Teams SDK App client
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
---------
Co-authored-by: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
* Migrate Teams adapter from botbuilder to @microsoft/teams.apps
Replace botbuilder/botframework-connector with @microsoft/teams.apps (TeamsSDK v2.0.6).
Breaking changes:
- Remove certificate auth (TeamsAuthCertificate) — use token or managedIdentityClientId instead
- Env vars changed: TEAMS_APP_ID → CLIENT_ID, TEAMS_APP_PASSWORD → CLIENT_SECRET, TEAMS_APP_TENANT_ID → TENANT_ID
- Reactions now work (addReaction/removeReaction) instead of throwing NotImplementedError
- Graph API calls use @microsoft/teams.graph-endpoints typed endpoints
Key changes:
- BridgeHttpAdapter captures TeamsSDK route handler for serverless dispatch
- Event handlers registered via app.on() (message, messageReaction, card.action, conversationUpdate, installationUpdate)
- Outbound calls use app.send() and app.api.conversations.activities()
- Stream support via post+edit (native HttpStream when SDK exports it)
- Graph API uses typed endpoints from @microsoft/teams.graph-endpoints
- isMention detection via activity entities instead of bot name matching
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
* Align TeamsAdapterConfig with AppOptions from TeamsSDK
- Replace custom config fields with Pick<AppOptions, ...>
clientId (was appId), clientSecret (was appPassword), tenantId (was appTenantId)
- Remove TeamsAuthFederated, appType — use managedIdentityClientId directly
- Constructor passes config through to App (App handles env var resolution)
- Use this.app.id instead of this.config.appId for bot identity checks
- 3 replay-fetch-messages tests have known failures (type shape mismatch)
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
* Remove api() getter, add handler type safety
- Remove useless private api() getter, use this.app.api directly
- Type handlers with specific activity types:
handleMessageActivity(ctx: IActivityContext<IMessageActivity>)
handleAdaptiveCardAction(ctx: IActivityContext<IAdaptiveCardActionInvokeActivity>)
handleReactionFromContext(ctx: IActivityContext<IMessageReactionActivity>)
- Remove unnecessary casts now that ctx.activity is properly typed
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
* Revert bot.tsx example changes from migration branch
Keep Azure OpenAI and DM handler changes as local-only, not part of the migration.
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
* Revert example package.json from migration branch
Keep only adapters.ts config changes, revert package.json dep changes.
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
* Remove unrelated skill files from migration branch
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
* Fix
* Update README for TeamsSDK migration
- New env vars: CLIENT_ID, CLIENT_SECRET, TENANT_ID
- New config: clientId, clientSecret, tenantId, token, managedIdentityClientId
- Remove certificate auth docs (dropped)
- Reactions now supported (add/remove)
- Typing indicator now supported
- Graph API uses @microsoft/teams.graph-endpoints
- Note streaming and modals status
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
* Revert "Update README for TeamsSDK migration"
This reverts commit 729fe463c1.
* update readme
* Refactor adapter-teams: extract types, errors, and graph-api modules
Split the 2,114-line index.ts into focused modules:
- types.ts: TeamsAdapterConfig, TeamsThreadId, TeamsChannelContext
- errors.ts: handleTeamsError as standalone pure function
- graph-api.ts: TeamsGraphReader class with all Graph API read methods
index.ts shrinks to ~1,133 lines, delegating graph reads to TeamsGraphReader
which receives dependencies via constructor injection.
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
* Clean up tests
* Do not break public facing config.
* Use previous .env vars
* Fix tests
* Fix webhook concurrency issue
* Remove support for reactions
* Fetch aadgroup id if it doesn't exist
* Fix lint and formatting issues in Teams adapter
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
* Fix README to match public API, fix 403 error mapping, correct NotImplementedError args, remove stale test aliases, and add changeset
* Fix remaining mockBotAdapter references and trailing whitespace
* fix graph pagination
---------
Co-authored-by: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Co-authored-by: Ben Sabic <bensabic@users.noreply.github.com>
* 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>
* Initial marketplace draft
* Update language
* Add logos to cards
* Redesign cards
* Update adapter-card.tsx
* Update adapter-card.tsx
* Migrate marketplace to adapters
* Update meta.json
* Split adapters into new three groups
* Add iMessage
* Move adapter docs to READMEs
* Cleanup docs
* Add more logos, implement shadcn ui components
* Update adapters.json
* Add Streamdown
* Fetch vercel readmes from workspace
* Update readme-content.tsx
* Upgrade Streamdown
* Update global.css
* Update adapters.json
* Update adapters.json
* Add links to docs
* List upcoming official adapters
* Update adapters.json
* Fix adapters links
* Fix typo
* Misc fixes
* Update adapters.json
* Update adapters.json
* Migrate new info
* Update pnpm-lock.yaml
* Update adapter-card.tsx
* Add postgres to adapters page
* Update adapter-card.tsx
* Migrate postgres docs
* Add pg to valid README imports
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
* Move feature matrices from docs to package READMEs
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
* Remove packages tables from adapter/state docs
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
* Genericize adapter/state doc descriptions
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
* fix: improve adapters page accessibility and empty state handling
* Add custom adapter building section to SKILL.md
* Use currentColor for GitHub, Linear, and Memory icons
* Use GitHub API for README fetch, add heading to fallback state
Use the GitHub REST API instead of raw.githubusercontent.com to
automatically resolve the repo's default branch, so community
adapters using master or other branch names work correctly.
* Update adapters-grid.tsx
---------
Co-authored-by: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Co-authored-by: Ben Sabic <bensabic@users.noreply.github.com>
* Initial work on postgres state store
* Run fix
* Replace pre-written CHANGELOG with proper changeset
The CHANGELOG was manually written with a version entry. This repo uses
Changesets to manage versioning, so add a proper changeset file instead.
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
* Add missing @vitest/coverage-v8 dev dependency
The vitest config specifies coverage provider "v8" but the package
was missing from devDependencies.
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
* Drop drizzle-orm, use raw postgres queries
The adapter only needs 3 simple tables with basic CRUD. drizzle-orm is
a full ORM that adds significant dependency weight for no real benefit
here. The ensureSchema() method was already using raw queries. This
halves the dependency surface to just the postgres driver.
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
* Add comprehensive unit tests for postgres state adapter
Test factory function edge cases (missing URL, env var fallbacks,
custom keyPrefix) and ensureConnected guard for all state operations.
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
* Document expired row cleanup limitation for postgres adapter
Unlike Redis, Postgres doesn't auto-delete expired rows. Document the
opportunistic cleanup behavior and provide SQL for periodic cleanup.
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
* Remove stale drizzle keyword from package.json
drizzle-orm was removed in a prior commit but the keyword remained.
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
* Fix expiresAt parsing in acquireLock
The postgres library returns timestamptz columns as JavaScript Date
objects, not strings. Wrapping in new Date() was unnecessary overhead.
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
* Document lock atomicity difference vs Redis adapters
The Postgres ON CONFLICT approach relies on row-level locking rather
than a single atomic SET NX PX like Redis. Note this for users who
need high-contention distributed locking.
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
* Use crypto.randomUUID() for lock token generation
Math.random() is not cryptographically secure and has a higher
collision risk in distributed environments. crypto.randomUUID() is
available in Node 16+ and provides better uniqueness guarantees.
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
* Delete CHANGELOG.md
* Add comprehensive unit tests for postgres state adapter
Mock the postgres module and SQL client to achieve 100% coverage
across statements, branches, functions, and lines. Tests cover
factory function, connection lifecycle, subscriptions, locking,
cache operations, and the owned-client disconnect path.
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
* Add setIfNotExists method and merge with main
Implement the setIfNotExists method added to the StateAdapter
interface since this branch diverged. Uses INSERT ... ON CONFLICT
DO NOTHING with RETURNING to atomically check-and-set.
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
* Add postgres to state docs navigation and memory adapter callout
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
* Migrate postgres state adapter from postgres to pg (node-postgres)
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
* Rename state-postgres to state-pg and align version to 4.17.0
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
---------
Co-authored-by: Hayden Bleasel <hello@haydenbleasel.com>
Co-authored-by: Claude Opus 4.6 <noreply@anthropic.com>
* feat(slack): support streaming task_update, plan_update, markdown_text chunks
- Add StreamChunk type for structured streaming content
- Extend stream() to accept AsyncIterable<string | StreamChunk>
- Add taskDisplayMode option to StreamOptions
- Slack adapter passes structured chunks directly to Slack's streaming API
- Handle url_verification before signature check to prevent Slack from
silently disabling event delivery when signing secret is misconfigured
- Add tests for StreamChunk handling in thread.post()
* review fixes: type safety, security, dedup
- Replace loose StreamChunk interface (index signature) with
discriminated union (MarkdownTextChunk | TaskUpdateChunk | PlanUpdateChunk)
- Move url_verification back after signature check to prevent
unauthenticated challenge responses
- Remove redundant AsyncIterable<string> from PostableMessage union
- Extract pushTextAndFlush helper to deduplicate markdown flush logic
- Remove unnecessary `as string` casts after type guards
- Export individual chunk types from package index
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
* add changeset
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
---------
Co-authored-by: Hayden Bleasel <hello@haydenbleasel.com>
Co-authored-by: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
* refactor: replace broad googleapis dependency with specific packages
* fix: reorder imports in gchat adapter
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
* Create tidy-groups-marry.md
---------
Co-authored-by: Hayden Bleasel <hello@haydenbleasel.com>
Co-authored-by: Claude Opus 4.6 <noreply@anthropic.com>
* Add support for telegram bot
* Add support for action buttons in telegram
* Use markdown parse mode
* Add more tests
* Fix linting issues
* Fixes
* Fixes
* Don't reverse order messages for AI and always answer for telegram
* Add telegram integration test
* Update telegram.json
* [chat-sdk] add support for streamed messages + example of AI bot
* Refactor to just post AI
* Permissions
* Permissions teams
* empty
* Replay tests
* Better fake stream
* fix-slack-bug
* changeset
---------
Co-authored-by: Malte Ubl <malte.ubl@gmail.com>