* 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(docs): add inline logo chips to hero description
Add brand logos (Slack, Microsoft Teams, Google Chat, Discord) as
inline chips prefixed before each product name in the hero section.
- Import existing SVG logos from lib/logos.tsx
- Create LogoChip component with whitespace-nowrap to prevent
wrapping between logo and its corresponding product name
- Update Hero component description type from string to ReactNode
- Keep plain text description for metadata/SEO
* fix(docs): logo chips line-height, comma wrapping, and spacing
- Use absolute positioning inside a zero-height inline-block container
so the logo doesn't increase line height
- Move trailing commas inside the whitespace-nowrap span via a suffix
prop to prevent hanging commas at line start
- Add 2px extra spacing on the right side of logos before names
* fix(docs): vertically center logos and add 2px left spacing
- Use align-middle + top-1/2 -translate-y-1/2 for true vertical
centering relative to the text line
- Add ml-[2px] on the outer nowrap span for left-side spacing
* fix(docs): widen hero description max-width on desktop
Bump from max-w-3xl (48rem) to sm:max-w-[52rem] so the first
product (Slack) stays on the first line at typical desktop widths.
* fix(docs): resolve lint issues
- Fix import order (react type import after next)
- Replace namespace import with named imports for logos
- Sort CSS classes per biome nursery rule
- Fix formatter line wrapping
---------
Co-authored-by: jamesvclements <jamesvclements@users.noreply.github.com>
* Fix Redis state typings for url and client options
* Wait for injected Redis clients to become ready
* Simplify Redis readiness guard
* Stabilize memory state TTL test with fake timers
* Handle Redis reconnect errors until client is ready
* Handle injected Redis client reconnects correctly
* feat: add concurrency strategies for overlapping messages (queue, debounce, concurrent)
## Problem
When multiple messages arrive on the same thread while a handler is still
processing, the SDK has only one behavior: **lock-and-drop**. The incoming
message is silently discarded (or force-released, which creates uncontrolled
concurrency). This is insufficient for most real-world use cases:
- **AI chatbots** lose user follow-up messages sent while the model is streaming
- **Customer support bots** miss messages entirely, breaking conversation flow
- **Collaborative editing bots** need to coalesce rapid corrections into one action
## Solution
Introduce a new `concurrency` option on `ChatConfig` with four strategies:
### `'drop'` (default, backward-compatible)
Existing behavior. Lock acquired or `LockError` thrown. No changes.
### `'queue'`
Messages that arrive while a handler is running are enqueued in the state
adapter. When the current handler finishes, the queue is drained: **only the
latest message is dispatched**, with all intermediate messages provided as
`context.skipped`. This gives the handler full visibility into what happened
while it was busy, without forcing it to re-process every message sequentially.
```typescript
const chat = new Chat({
concurrency: 'queue',
// ...
});
chat.onNewMention(async (thread, message, context) => {
if (context && context.skipped.length > 0) {
// "You sent 4 messages while I was thinking. Responding to your latest."
const allMessages = [...context.skipped, message];
// Pass all messages to the LLM for full context
}
});
```
Flow:
```
A arrives → acquire lock → process A
B arrives → lock busy → enqueue B
C arrives → lock busy → enqueue C
D arrives → lock busy → enqueue D
A done → drain: [B, C, D] → handler(D, { skipped: [B, C] })
D done → queue empty → release lock
```
### `'debounce'`
Every message (including the first) starts or resets a debounce timer. Only the
**final message in a burst** is processed. The lock-holding function stays alive
through `waitUntil` during the debounce window.
```typescript
const chat = new Chat({
concurrency: { strategy: 'debounce', debounceMs: 1500 },
// ...
});
```
Flow:
```
A arrives → acquire lock → store A as pending → sleep(debounceMs)
B arrives → lock busy → overwrite pending with B (A dropped)
C arrives → lock busy → overwrite pending with C (B dropped)
... debounceMs elapses with no new message ...
→ process C → release lock
```
### `'concurrent'`
No locking at all. Every message is processed immediately in its own handler
invocation. Suitable for stateless handlers (lookups, translations) where
thread ordering doesn't matter.
```typescript
const chat = new Chat({
concurrency: 'concurrent',
// ...
});
```
## API Surface
### ChatConfig
```typescript
interface ChatConfig {
concurrency?: ConcurrencyStrategy | ConcurrencyConfig;
/** @deprecated Use `concurrency` instead */
onLockConflict?: 'force' | 'drop' | ((threadId, message) => ...);
}
type ConcurrencyStrategy = 'drop' | 'queue' | 'debounce' | 'concurrent';
interface ConcurrencyConfig {
strategy: ConcurrencyStrategy;
maxQueueSize?: number; // Default: 10
onQueueFull?: 'drop-oldest' | 'drop-newest'; // Default: 'drop-oldest'
queueEntryTtlMs?: number; // Default: 90_000 (90s)
debounceMs?: number; // Default: 1500
maxConcurrent?: number; // Default: Infinity
}
```
### MessageContext (new, passed to handlers)
```typescript
interface MessageContext {
skipped: Message[]; // Intermediate messages, chronological
totalSinceLastHandler: number; // skipped.length + 1
}
```
All handler types (`MentionHandler`, `MessageHandler`, `SubscribedMessageHandler`,
`DirectMessageHandler`) now accept an optional `MessageContext` as their last
parameter. Existing handlers that don't use it are unaffected.
### StateAdapter (new methods)
```typescript
interface StateAdapter {
enqueue(threadId: string, entry: QueueEntry, maxSize: number): Promise<number>;
dequeue(threadId: string): Promise<QueueEntry | null>;
queueDepth(threadId: string): Promise<number>;
}
```
Implemented across all four state adapters:
- **MemoryStateAdapter**: in-process array
- **RedisStateAdapter**: Lua script (RPUSH + LTRIM + PEXPIRE)
- **IoRedisStateAdapter**: same Lua approach
- **PostgresStateAdapter**: new `chat_state_queues` table with atomic dequeue
## Architecture
`handleIncomingMessage` was refactored into composable pieces:
- `dispatchToHandlers()` — shared handler dispatch logic (mention detection,
subscription routing, pattern matching). Extracted from the old monolithic
method so all strategies reuse it.
- `handleDrop()` — original lock-or-fail path (preserves `onLockConflict` compat)
- `handleQueueOrDebounce()` — enqueue if busy, drain or debounce after
- `handleConcurrent()` — skip locking entirely
- `drainQueue()` — collect all pending, dispatch latest with skipped context
- `debounceLoop()` — sleep/check/repeat until no new messages arrive
## Queue Entry TTL
Queued messages have a configurable TTL (`queueEntryTtlMs`, default 90s). Stale
entries are discarded on dequeue with a `message-expired` log event. This
prevents unbounded accumulation and ensures handlers don't process messages
that are no longer relevant.
## Observability
All strategies emit structured log events at `info` level:
| Event | Strategy | Data |
|-----------------------|------------------|---------------------------------------|
| `message-queued` | queue | threadId, messageId, queueDepth |
| `message-dequeued` | queue, debounce | threadId, messageId, skippedCount |
| `message-dropped` | drop, queue | threadId, messageId, reason |
| `message-expired` | queue, debounce | threadId, messageId |
| `message-superseded` | debounce | threadId, droppedId |
| `message-debouncing` | debounce | threadId, messageId, debounceMs |
| `message-debounce-reset` | debounce | threadId, messageId |
## Backward Compatibility
- Default remains `'drop'` — zero breaking changes for existing users
- `onLockConflict` continues to work but is marked `@deprecated`
- Handler signatures are backward-compatible (new `context` param is optional)
- Deduplication always runs regardless of strategy
## Files Changed
- `packages/chat/src/types.ts` — new types, updated handler signatures
- `packages/chat/src/chat.ts` — strategy routing, drain/debounce loops
- `packages/chat/src/index.ts` — export new types
- `packages/chat/src/mock-adapter.ts` — queue methods for test mock
- `packages/state-memory/src/index.ts` — in-memory queue
- `packages/state-redis/src/index.ts` — Redis queue (Lua)
- `packages/state-ioredis/src/index.ts` — ioredis queue (Lua)
- `packages/state-pg/src/index.ts` — Postgres queue table
- `packages/chat/src/chat.test.ts` — tests for queue, debounce, concurrent
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
* test: comprehensive test coverage for concurrency strategies and queue methods
Add tests across all state adapters and the Chat class:
**MemoryStateAdapter** (8 new tests):
- enqueue/dequeue single entry
- dequeue from empty queue returns null
- dequeue from nonexistent thread returns null
- queueDepth returns 0 for empty queue
- FIFO ordering across multiple entries
- maxSize trimming (keeps newest)
- maxSize=1 debounce behavior (last-write-wins)
- queue isolation by thread
- queue cleared on disconnect
**PostgresStateAdapter** (8 new tests):
- INSERT query for enqueue
- overflow trimming query
- depth return value
- parsed entry from dequeue
- null from empty dequeue
- atomic DELETE-RETURNING for dequeue
- queueDepth return value
- zero depth for empty queue
**RedisStateAdapter / IoRedisStateAdapter** (3+3 existence checks):
- enqueue, dequeue, queueDepth method existence
**Chat concurrency** (5 new tests):
- drop-newest policy when queue is full
- drop-oldest policy evicts oldest entries
- expired entries skipped during drain
- onNewMessage pattern handlers receive context
- onSubscribedMessage handlers receive skipped context
Total new tests: 27 (780 chat + 33 memory + 59 pg)
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
* Address feedback
* Support a channel locking strategy, make it default for WhatsApp and Telegram
* docs: fix typo "Committment" → "Commitment" (#274)
Co-authored-by: Arif Kobel <arif.kobel@phorax.com>
* Add webhook verification to GChat (#287)
- Issues a warning if required env vars are not present (also for telegram)
- Makes telegram use a time-safe verifier
* Make adapters depend on `chat` as a real dep (#289)
Without this, changeset will make any dep change a major change
* Version Packages (#290)
Co-authored-by: github-actions[bot] <41898282+github-actions[bot]@users.noreply.github.com>
* Fix serialization
---------
Co-authored-by: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Co-authored-by: Arif Kobel <102538661+ArifKobel@users.noreply.github.com>
Co-authored-by: Arif Kobel <arif.kobel@phorax.com>
Co-authored-by: github-actions[bot] <41898282+github-actions[bot]@users.noreply.github.com>
* feat: add adpater disconnect hook
* fix: use Promise.allSettled for resilient shutdown, add docs and changeset
- Use Promise.allSettled so one failing adapter doesn't prevent others from disconnecting
- Add test for error resilience during shutdown
- Remove unnecessary type guards in tests
- Fix lint: sort interface members, format test file
- Add changeset (minor bump for chat)
- Document disconnect hook in API docs and adapter building guide
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>
* feat(telegram): convert entities to markdown in parsed messages
Telegram delivers formatting (bold, italic, links, code, etc.) as separate
entity objects alongside plain text. Previously, parseTelegramMessage only
used the raw text, losing all entity information — most critically, text_link
entity URLs were dropped entirely.
This adds applyTelegramEntities() which reconstructs markdown syntax from
entities before storing the message text. Supported entity types: text_link,
bold, italic, code, pre, and strikethrough. Other entity types (url, mention,
bot_command) are already present in the text and left unchanged.
Also adds the missing `url` and `language` fields to TelegramMessageEntity.
* chore: add changeset for telegram entity markdown conversion
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>
* fix(telegram): set parse_mode for markdown messages
postMessage and editMessage only set parse_mode when a card was present,
causing markdown messages to render as plain text with visible formatting
characters. Now also sets parse_mode when the message has a markdown field.
Closes#226
* refactor: extract resolveParseMode helper to deduplicate postMessage/editMessage
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
---------
Co-authored-by: Ben Sabic <bensabic@users.noreply.github.com>
Co-authored-by: Hayden Bleasel <hello@haydenbleasel.com>
Co-authored-by: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
* fix(chat): correctly type thread and channel interface
* fix(chat): nit make comment match `Message` comment for consistency
* chore: add changeset for thread/channel type fix
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>
AI SDK v5 renamed the `step-finish` event to `finish-step`, causing
`fromFullStream()` to silently ignore step boundaries and concatenate
multi-step agent output without paragraph separators.
Closes#238
Co-authored-by: Ben Sabic <bensabic@users.noreply.github.com>
* feat(slack): resolve @displayname mentions to <@USER_ID> in outgoing messages
Build a reverse index from display name → user IDs during lookupUser(),
track thread participants on incoming messages, and resolve @name mentions
to Slack's <@USER_ID> format before sending. Disambiguates using thread
participants when multiple users share a display name. Also increases
user/channel cache TTLs to 8 days.
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
* fix(slack): use replace offset for duplicate mentions, invalidate cache on user_change
* Revert "fix(slack): use replace offset for duplicate mentions, invalidate cache on user_change"
This reverts commit 8cb0d908d2.
* fix(slack): use replace offset for duplicate mentions, invalidate cache on user_change (#236)
* fix(slack): use replace offset for duplicate mentions, invalidate cache on user_change
* style: align handleUserChange to async/await, expand changeset
---------
Co-authored-by: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Co-authored-by: Matan Kushner <hello@matchai.dev>
* fix(github): accumulate stream before posting, log fallback edit errors
The GitHub adapter relied on chat-sdk's default fallbackStream which
posts a placeholder then edits it every 500ms. GitHub returns 422 on
these edits because body is empty during TTFT, and rapid edits risk
secondary rate limits.
Add stream() to GitHubAdapter that accumulates the full text before
posting once. Also log fallbackStream edit errors instead of silently
swallowing them.
* test: add tests for GitHub adapter stream() and fallbackStream logging
* style: format with biome
* refactor: use Logger instead of console.warn in fallbackStream
Plumb the Chat logger into ThreadImpl so fallbackStream uses the
structured logger instead of raw console.warn.
* test: simplify fallbackStream logging test
Reuse mockLogger from mock-adapter.ts and rely on createMockAdapter's
default editMessage mock instead of re-specifying the resolved value.
* test: use vi.mocked, drop redundant stream=undefined
* chore: add changeset
* refactor: extract accumulateStream utility, deduplicate GitHub and WhatsApp adapters
* Revert "refactor: extract accumulateStream utility, deduplicate GitHub and WhatsApp adapters"
This reverts commit 0a3ca3cc9a.
* feat(slack): resolve bare channel mentions to display names
Mirror the existing lookupUser() pattern to resolve bare <#C123> channel
mentions into <#C123|channelName> via conversations.info API with caching.
Channels with existing labels (<#C123|general>) are left unchanged.
Also fix 22 pre-existing test timeouts in sandboxed environments by
adding botUserId to skip real auth.test calls, and mocking users.info
where parseSlackMessage/handleSlashCommand calls lookupUser.
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
* Replay test
* changeset
---------
Co-authored-by: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
* feat: add LinkPreview to Message for URL and embedded message support
Add `links: LinkPreview[]` to `Message` so handlers can access URLs
shared in messages. Each LinkPreview contains the URL and optional
unfurl metadata (title, description, siteName, imageUrl).
On Slack, links are extracted from rich_text block elements (falling
back to <url> patterns in text). Links pointing to other Slack messages
(*.slack.com/archives/{channel}/p{ts}) include a `fetchMessage()`
callback that retrieves and parses the linked message.
`toAiMessages()` now appends link metadata to message content
automatically, labeling embedded message links distinctly so AI models
understand the context.
- Add LinkPreview interface to core types
- Add links field to Message, MessageData, SerializedMessage
- Extract links in Slack adapter (blocks + text fallback)
- Provide fetchMessage for Slack message URLs
- Set links: [] in all other adapters
- Include link metadata in toAiMessages() output
- Document LinkPreview in message API docs
- Document toAiMessages() in streaming and handling-events docs
- Add toAiMessages to API overview
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
* fix: remove redundant links: [] from adapters for backwards compatibility
The Message constructor already defaults links to [] when not provided,
so adapters that don't support link extraction don't need to pass it
explicitly. This makes the change backwards-compatible for third-party
adapters — they get an empty links array without any code changes.
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
* fix: avoid polynomial regex in Slack link extraction
Replace `[^>|]+` pattern (which backtracks on `|`) with `[^>]+`
and a programmatic indexOf split. This prevents ReDoS on untrusted
message text.
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
* feat: add attachment support and mention tests to toAiMessages
toAiMessages now includes image and text-file attachments as multipart
content compatible with AI SDK's UserContent type:
- Images → ImagePart (via fetchData base64 or URL fallback)
- Text files (text/*, application/json, etc.) → FilePart
- Video/audio → warns via onUnsupportedAttachment callback
- Other file types → silently skipped
The function is now async to support fetchData() calls for inlining
attachment data as base64 data URIs. When fetchData fails, falls back
to the attachment URL.
Also adds mention rendering tests verifying that @mentions appear as
@name (not Slack's <@U123> syntax) in toAiMessages output, both in
plain messages and with links/includeNames enabled.
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
* fix: make AiMessage types structurally compatible with AI SDK
Use a discriminated union (AiUserMessage | AiAssistantMessage) so
AiMessage[] is directly assignable to ModelMessage[] without casts.
Match DataContent type (string | Uint8Array | ArrayBuffer | Buffer)
for image/file parts to ensure structural compatibility.
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
* fix(example): pass image attachments to AI via toAiMessages
The onNewMention handler was passing message.text directly to the AI
agent, dropping any image attachments. Now uses toAiMessages([message])
which includes images via fetchData as base64 inline data, enabling
the AI to actually see uploaded images.
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
* fix: pass image data as Buffer, not data URI
The AI SDK expects DataContent (Buffer/Uint8Array/base64 string) for
image and file parts, not data URIs. Passing `data:image/png;base64,...`
caused "Could not process image" errors from the API. Now passes the
raw Buffer from fetchData() directly, with mediaType set separately.
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
* fix: require fetchData for attachments, no URL fallback
Slack's url_private requires Bearer token auth that AI providers can't
provide. Remove URL fallback — attachments are only included when
fetchData() succeeds (which handles auth internally). Log errors
instead of silently falling through.
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
* debug: add temporary logging to toAiMessages image handling
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
* fix: use data URIs for image/file attachments in toAiMessages
The AI SDK's convertToLanguageModelV2DataContent parses data: URIs
to extract both the base64 content and media type. Raw base64 strings
lose the media type (returns mediaType: void 0), and raw Buffers may
not serialize correctly across network boundaries. Data URIs are the
most reliable format.
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
* debug: log fetched image size to diagnose API rejection
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
* debug: log prompt structure to diagnose image rejection
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
* fix: use FilePart instead of ImagePart for image attachments
The AI SDK's ImagePart with data URI strings doesn't work correctly
through the AI Gateway. Use FilePart (type: "file") with data URI
in the data field instead — this matches the working pattern used by
other projects and handles image data correctly across all providers.
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
* debug: comprehensive logging at every decision point in toAiMessages
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
* debug: log data prefix to verify content format
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
* debug: log token capture and detect HTML responses from Slack file fetch
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
* fix: improve Slack file fetch error message and remove debug logging
When Slack returns an HTML login page instead of file data (typically
due to missing "files:read" OAuth scope), the error message now
explicitly tells the user what scope to add. Also removes all
temporary debug logging from toAiMessages and createAttachment.
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
* Add transform support
* changeset
* address-feedback
* lint
---------
Co-authored-by: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
* Improve WhatsApp adapter README
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
* Rename WhatsApp adapter to WhatsApp Business Cloud
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
---------
Co-authored-by: Claude Opus 4.6 (1M context) <noreply@anthropic.com>