Skip to content

Commit c43ea05

Browse files
release: 0.4.27 — synced to upstream vercel/chat@4.27.0 (#106)
Synced to upstream vercel/chat@4.27.0 (release commit f55378a). Upstream parity ports: chat.get_user across 8 adapters (#90), Slack Socket Mode (#86, closes #68), Slack dynamic bot_token resolver + webhook_verifier (#87), Teams native DM streaming (#88), Telegram MarkdownV2 (#89), Discord card text dedup (#89), Slack streaming team_id + Teams DM Graph conversation IDs (#85), Slack adapter bug-fix sweep (#89), ExternalSelect.initial_option + option_groups (#84), Slack @mention email-safe regex (#91). Python-only improvements: markdown parser completeness (#101), streaming markdown list/table chunk-boundary (#99), Slack files_upload_v2 kwarg fix (#103), dict-shaped StreamChunk support (#105), Google Chat card-text markdown rendering (#92), adapter init logs + adapter-list in not-found errors (#104). Sync-process documentation captures the review-loop discipline lessons from this wave (docs/UPSTREAM_SYNC.md + docs/SELF_REVIEW.md). Fidelity workflow stays pinned to chat@4.26.0 — upstream did not publish a chat@4.27.0 tag for the Apr 30 monorepo cut. 4036 tests pass, 3 skipped, 0 failed.
1 parent ff27651 commit c43ea05

7 files changed

Lines changed: 214 additions & 67 deletions

File tree

CHANGELOG.md

Lines changed: 38 additions & 52 deletions
Original file line numberDiff line numberDiff line change
@@ -1,75 +1,61 @@
11
# Changelog
22

3-
## 0.4.27a1 (2026-05-07)
3+
## 0.4.27 (2026-05-28)
44

5-
Alpha sync starter for upstream `4.27.0` (`vercel/chat` release commit
6-
`f55378a`, Apr 30 2026). **No feature ports in this release** — this is a
7-
parity-bookkeeping bump that establishes the sync branch, sets
8-
`UPSTREAM_PARITY = "4.27.0"`, and lays out the porting plan below. Each
9-
substantive commit lands as its own PR (matching the cadence used during
10-
the `4.26.0` sync: #64, #66, #67, #74, etc.).
5+
Synced to upstream `vercel/chat@4.27.0` (release commit `f55378a`, Apr 30 2026). Highlights: Slack Socket Mode + dynamic bot-token resolver, Teams native DM streaming, `chat.get_user()` across all 8 adapters, Telegram MarkdownV2 rendering, and a sweep of adapter bug fixes. Sets `UPSTREAM_PARITY = "4.27.0"`.
116

12-
### Upstream tagging note
7+
### Upstream parity ports
8+
9+
#### Core (`packages/chat`)
1310

14-
Upstream cut versions for the entire monorepo on Apr 30 2026 (commit
15-
`f55378a`), bumping `packages/chat/package.json` from `4.26.0` to
16-
`4.27.0`. As of this writing only `@chat-adapter/shared@4.27.0` got a
17-
git tag — no `chat@4.27.0` tag was published. The fidelity workflow
18-
(`scripts/verify_test_fidelity.py`, `.github/workflows/lint.yml`)
19-
therefore stays pinned to `chat@4.26.0` until either the tag is
20-
published upstream or the first feature port lands and we move the pin
21-
to commit `f55378a` directly. Local devs running fidelity in baseline
22-
mode will see a `chat@4.26.0` vs `chat@4.27.0` mismatch — that's the
23-
intended in-flight signal.
11+
- **`Chat.get_user(adapter, user_id)`** for cross-platform user lookups (#90, vercel/chat#391). Returns `User | None` with `email`, `display_name`, `avatar_url`, `is_bot` populated from each platform's user-lookup API. Every adapter exposes `async def get_user(user_id)`; Telegram is best-effort (`getChat` only), WhatsApp returns minimal user info (Cloud API has no separate lookup).
12+
- **`ExternalSelect.initial_option` + `option_groups`** (#84, vercel/chat#410, #397). Type extensions on `ExternalSelect`; Slack adapter serializes `option_groups` to Block Kit.
13+
- **`concurrency.max_concurrent` honored in `concurrent` strategy** (vercel/chat#419) — already enforced in the Python port via `asyncio.Semaphore`; upstream has caught up. Divergence row in `docs/UPSTREAM_SYNC.md` downgrades from "silent correctness bug upstream" to "behavior parity restored".
2414

25-
### Sync scope (22 substantive upstream commits between `chat@4.26.0..f55378a`)
15+
#### Slack (`packages/adapter-slack`)
2616

27-
#### Core (`packages/chat/``src/chat_sdk/`)
17+
- **Socket Mode transport** (#86, vercel/chat#162). New `SlackAdapterConfig(mode="socket", app_token="xapp-...")` opens a persistent WebSocket via `slack_sdk.socket_mode.aiohttp.SocketModeClient`. Outer reconnect loop (1s → 30s exp backoff, 250ms shutdown poll) layered on top of the SDK's auto-reconnect. Forwarded-events receiver in `handle_webhook` for the serverless variant (`x-slack-socket-token`, `hmac.compare_digest`). `ModalResponse(action="clear")` lands too. New optional extra: `chat-sdk-python[slack-socket]`. Closes #68.
18+
- **Dynamic `bot_token` resolver + custom `webhook_verifier`** (#87, vercel/chat#421). `bot_token` now accepts `str | Callable[[], str | Awaitable[str]]`; resolver is invoked per request and cached in a per-instance ContextVar so concurrent webhooks don't share tokens. `webhook_verifier` replaces built-in HMAC + timestamp verification (returning a `str` substitutes the canonical body). `signing_secret` precedence over `webhook_verifier` preserved. `schedule_message().cancel()` and `Attachment.fetch_data` are rotation-safe. New `SlackAdapter.current_token_async()` for cron-style callers outside `handle_webhook`.
19+
- **Slack streaming team_id fix for interactive payloads** (#85, vercel/chat#330). `recipient_team_id` extraction now walks `team_id``team` (string) → `team.id` (object) → `user.team_id` in order, returning `None` only when no string ID is found. Previously the entire `team` dict was forwarded for `block_actions`, breaking streaming routing.
20+
- **Link-preview unfurl enrichment** (#89, vercel/chat#395). `message_changed` events are routed through a new `_handle_message_changed` handler with a 2s poll window and per-event link cache (1h TTL), so the message handler sees enriched links.
21+
- **`@mention` regex preserves email addresses** (#91, vercel/chat#394). The `@user` matcher now skips `@` characters inside email localparts.
22+
- **Empty `thread_ts` guard** (#89, vercel/chat#292). `stream()` now degrades to a single `post_message` for empty `thread_ts` instead of raising — top-level Slack DMs encode thread IDs with an empty `thread_ts` by design, and the old `ValidationError` silently dropped the reply.
2823

29-
- [x] (PR #90) **`chat.getUser(adapter, userId)`** for cross-platform user lookups (vercel/chat#391, upstream commit `a520797`). Adapter-side: each adapter exposes `getUser`. Touches `chat.py`, `types.py` (`User` type extension), and every adapter (`slack`, `teams`, `gchat`, `telegram`, `discord`, `whatsapp`, `github`, `linear`).
30-
- [x] (PR #84) **`ExternalSelect.initial_option` + `option_groups`** (vercel/chat#410, `70281dc`). Type extension in `types.py`; Slack adapter must serialize `option_groups` to Block Kit.
31-
- [x] (already merged via PR #74) **`thread.post()` streaming options** (vercel/chat#388, `9093292`). New params plumb through `Thread.post``chat.py` orchestrator.
32-
- [x] (PR #85) **Slack streaming team ID fix for interactive payloads** (vercel/chat#330, `8a0c7b3`). Bug fix in the Slack streaming path; check `adapters/slack/adapter.py` request-context plumbing.
33-
- [⏭️] (out of scope) **Bundled guide markdown + templates manifest** (vercel/chat#423, `b0ab804`). Decision: skip or copy `packages/chat/resources/guides/*.md` and `templates.json` verbatim. Probably skip — these are TS-monorepo authoring resources, not runtime behavior.
34-
- [x] **`concurrency.maxConcurrent` honored in `concurrent` strategy** (vercel/chat#419, `d630e6c`). Already addressed in the Python port — see the existing `ConcurrencyConfig.max_concurrent` row in `docs/UPSTREAM_SYNC.md` (we enforce via `asyncio.Semaphore` and reject misconfiguration). Upstream has now caught up; on this sync the divergence row downgrades from "silent correctness bug upstream" to "behavior parity restored".
24+
#### Teams (`packages/adapter-teams`)
3525

36-
#### Slack (`packages/adapter-slack/``src/chat_sdk/adapters/slack/`)
26+
- **Native streaming for DMs via emit** (#88, vercel/chat#416). DM threads use the Bot Framework streaming protocol (`channelData.streamType=streaming` + `streamSequence`, then a final `streamType=final` message); group chats accumulate and post once (matches upstream's flicker-free behavior). New `TeamsAdapterConfig.native_stream_min_emit_interval_ms` (default 1500ms) honors Teams' ~1 req/sec quota; `StreamOptions.update_interval_ms` overrides. Send-failure mid-stream cancels the session and re-raises so `Thread.stream` history matches user-visible text. Migration to `microsoft-teams-apps` (Python SDK, GA 2026-05-01) tracked as #93 for 0.4.28.
27+
- **DM conversation ID resolution for Graph API** (#85, vercel/chat#403). Bot Framework opaque DM IDs are rejected by Graph's `/chats/{chat-id}/messages` endpoint; the adapter now caches the user's `aadObjectId` from inbound activities into a `TeamsDmContext` keyed by base conversation ID and resolves to the canonical `19:{userAadId}_{botId}@unq.gbl.spaces` form on Graph calls.
3728

38-
- [x] (PR #86) **Slack Socket Mode support** (vercel/chat#162, `7e9d0fc`). Big — adds a persistent WebSocket transport alongside HTTP webhooks. Decision: in scope or follow-up? Mirrors the Discord Gateway gap already documented in non-parity ("HTTP interactions only").
39-
- [x] (PR #87) **Dynamic `bot_token` resolver + custom `webhookVerifier`** (vercel/chat#421, `2531e9c`). Multi-workspace pattern; touches `SlackAdapter.__init__` and request handling.
40-
- [x] (PR #84) **External-select Block Kit support** (vercel/chat#397, `a179b29`). Pairs with the core `option_groups` change above.
41-
- [ ] **Native `markdown_text` for outgoing messages** (vercel/chat#440, post-release — Apr 17). NOTE: this commit is post-`f55378a` so technically out of `4.27.0` scope, but listed here because the team often picks up post-release fixes.
42-
- [x] (PR #89) **Link-preview unfurl metadata enrichment** (vercel/chat#395, `ded6f78`).
43-
- [x] (PR #89) **`@mention` regex preserves email addresses** (vercel/chat#394, `c26ee6c`).
44-
- [x] (PR #89) **Guard against empty `threadTs` (`invalid_thread_ts` fix)** (vercel/chat#292, `53c6b68`).
29+
#### Telegram (`packages/adapter-telegram`)
4530

46-
#### Teams (`packages/adapter-teams/``src/chat_sdk/adapters/teams/`)
31+
- **MarkdownV2 rendering** (#89, vercel/chat#407). Replaces the legacy `Markdown` parse_mode with `MarkdownV2`. Three escape contexts (normal text, code blocks, inline-link URLs) handle the spec's 18-char escape set per region.
4732

48-
- [x] (PR #88) **Native streaming for DMs via `emit`** (vercel/chat#416, `ed46bae`). Currently the Python port falls back to `_fallback_stream` for Teams; native streaming would lift that.
49-
- [x] (PR #85) **DM conversation ID resolution for Graph API** (vercel/chat#403, `4c24c94`). Bug fix.
50-
- [x] **Teams SDK 2.0.8 + `User-Agent` header** (vercel/chat#415, `885a471`). **N/A — JS-only.** Upstream's change bumps the `botbuilder` dependency and flips the bot client header from `X-User-Agent` to `User-Agent: Vercel.ChatSDK`. The Python Teams adapter does not depend on `botbuilder` (uses raw `aiohttp`), so there is no equivalent dependency to bump. The optional `User-Agent` header propagation is a defense-in-depth nice-to-have; documented as a deferred enhancement in `docs/UPSTREAM_SYNC.md` rather than landed in this sync.
33+
#### Discord (`packages/adapter-discord`)
5134

52-
#### Telegram
35+
- **Card text deduplication** (#89, vercel/chat#256). Card posts omit `content` on create (Discord renders both `content` and the embed otherwise); edits explicitly send `content: ""` so leftover text from a previous edit is cleared.
5336

54-
- [x] (PR #89) **MarkdownV2 rendering fixes** (vercel/chat#407, `b9a1961`). Pairs with the streaming-chunk safety trim in vercel/chat#446 (post-`f55378a`).
37+
### Python-only improvements
5538

56-
#### Discord
39+
- **Markdown parser completeness** (#101). GFM task lists (`- [ ]` / `- [x]``checked: bool`), backslash-escaped delimiters (lookbehind `(?<!\\)` on inline regexes), inline math (`$x$`) preserved by `_remend` and the format converter. Sentinel-based escape protection prevents pathological backslash sequences from being eaten by emphasis/strikethrough regexes.
40+
- **Streaming markdown list-marker awareness + table chunk-boundary** (#99, issue #69). `_get_committable_prefix` knows about list-marker positions so a chunk boundary lands cleanly; tables that span chunk boundaries are wrapped so the first chunk doesn't ship a half-table.
41+
- **`SlackAdapter._upload_files`** uses `channel=` not `channel_id=` for `files_upload_v2` (#103, issue #102). The underlying `files_completeUploadExternal` forwards `channel_id=channel` internally, so caller-supplied `channel_id=` collided and raised `TypeError` on every Slack file upload.
42+
- **Adapter dict-StreamChunk support** (#105). `slack`, `github`, and `google_chat` stream loops now honor the dict-shaped `{"type": "markdown_text", ...}` chunks that `thread.py`'s `_from_full_stream` has always forwarded (Teams already honored). Slack `send_structured_chunk` rewritten with a `_read()` helper for dict/dataclass uniformity; fallback warning message rewritten to name the actual possible causes.
43+
- **Google Chat card text rendering** (#92). `GoogleChatFormatConverter` now uses the full markdown parser for card text (was a regex stub that dropped formatting).
44+
- **Adapter init logs + adapter-list in not-found errors** (#104). `GoogleChatAdapter.initialize()` and `GitHubAdapter.initialize()` now log on init (matching Slack/Teams). `Chat.channel()` / `Chat.thread()` "adapter not found" errors append `(registered adapters: [...])` so operators can disambiguate "never constructed" from "wrong lookup name".
5745

58-
- [x] (PR #89) **Don't duplicate text when posting card messages** (vercel/chat#256, `7e5b447`). Confirm Python port's `discord/cards.py` doesn't have the same bug.
46+
### Sync-process documentation
5947

60-
#### Out of scope for this Python port
48+
- **Review-loop discipline** (`docs/UPSTREAM_SYNC.md`, `docs/SELF_REVIEW.md`). Codifies the lessons learned from this wave: self-review before opening the PR (cheaper than bot rounds), trace fix cascades across overlapping PRs, prefer official SDKs over hand-rolled implementations, cap drafts to 3–4 in flight, divergence budget of ≤2 per sync PR. `docs/SELF_REVIEW.md` adds adversarial check categories (input sweeps, emit/parse symmetry, pass-interaction, unforgeable sentinels, rebind/state coherence).
6149

62-
- **`@chat-adapter/web`** — new package adding a browser chat UI for chat-sdk bots (vercel/chat#444). No browser runtime in chat-sdk-python.
63-
- **Documentation site changes**`apps/docs/`, README/changelog tweaks, dependency bumps.
50+
### Upstream items not ported
6451

65-
### Workflow
52+
- **`@chat-adapter/web`** (Vue + Svelte browser UI, vercel/chat#444) — no browser runtime in chat-sdk-python.
53+
- **Teams SDK 2.0.8 + `User-Agent` header** (vercel/chat#415) — JS-only. The Python Teams adapter uses raw `aiohttp`, not `botbuilder`; tracked in `docs/UPSTREAM_SYNC.md` non-parity table as a deferred enhancement.
54+
- **Bundled guide markdown + templates manifest** (vercel/chat#423) — TS-monorepo authoring resources, not runtime behavior.
55+
56+
### Upstream tagging note
6657

67-
1. This alpha PR establishes the sync. CI on this draft is intentionally not invoked (lint.yml is gated on `!github.event.pull_request.draft`).
68-
2. Each item above lands as its own PR, following the same pattern as the `4.26.0` cycle. Each port PR:
69-
- Updates the relevant `MAPPING` / fidelity coverage and removes its entries from `scripts/fidelity_baseline.json` if previously baselined.
70-
- Bumps lint.yml's pinned upstream ref to commit `f55378a` (or a later SHA if upstream cuts a `chat@4.27.0` tag in the meantime).
71-
- Adds an entry under the next `CHANGELOG` heading (`0.4.27a2`, `0.4.27a3`, …).
72-
3. Once all 22 items are ported (or explicitly documented as divergence in `docs/UPSTREAM_SYNC.md`), the final PR cuts `0.4.27` and switches CI back to strict fidelity at the upstream tag.
58+
Upstream cut versions for the entire monorepo on Apr 30 2026 (commit `f55378a`), but only `@chat-adapter/shared@4.27.0` got a git tag — no `chat@4.27.0` tag was published. The fidelity workflow (`scripts/verify_test_fidelity.py`, `.github/workflows/lint.yml`) stays pinned to `chat@4.26.0` for this release; it'll move to a 4.27 SHA pin (or a real tag if upstream publishes one) in the next sync.
7359

7460
## 0.4.26.3 (2026-05-07)
7561

CLAUDE.md

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,7 @@
11
# Claude Code Quick Reference -- chat-sdk-python
22

33
## What is this?
4-
Python port of [Vercel Chat SDK](https://github.com/vercel/chat) (porting v4.27.0; last fully-synced release `0.4.26.3` at upstream `4.26.0`). Multi-platform async chat framework.
4+
Python port of [Vercel Chat SDK](https://github.com/vercel/chat) (synced to v4.27.0; last fully-synced release `0.4.27`). Multi-platform async chat framework.
55

66
## Key Commands
77
```bash
@@ -23,8 +23,8 @@ Our version embeds the upstream Vercel Chat version: `0.{upstream_major}.{upstre
2323
- `0.4.25` = synced to upstream `4.25.0`
2424
- `0.4.25.1` = Python-only fix on top of `4.25.0`
2525
- `0.4.26` = synced to upstream `4.26.0`
26-
- `0.4.26.3` = last fully-synced release (Python-only fixes on top of `4.26.0`)
27-
- `0.4.27a1` = alpha while porting upstream `4.27.0` (current branch)
26+
- `0.4.26.3` = Python-only fixes on top of `4.26.0`
27+
- `0.4.27` = synced to upstream `4.27.0` (current release)
2828
- `UPSTREAM_PARITY` constant in `__init__.py` = programmatic access
2929

3030
## Architecture

README.md

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -7,7 +7,7 @@
77

88
Multi-platform async chat SDK for Python. Port of [Vercel Chat](https://github.com/vercel/chat).
99

10-
> **Status: Alpha (0.4.27a1porting [Vercel Chat 4.27.0](https://github.com/vercel/chat))** — API may change. Last fully-synced release: `0.4.26.3` (parity with upstream `chat@4.26.0`). See [CHANGELOG.md](CHANGELOG.md) for the in-flight sync plan.
10+
> **Status: 0.4.27**synced to upstream [Vercel Chat 4.27.0](https://github.com/vercel/chat) (release commit `f55378a`). See [CHANGELOG.md](CHANGELOG.md) for release notes and [docs/UPSTREAM_SYNC.md](docs/UPSTREAM_SYNC.md) for the known non-parity list.
1111
1212
## Why chat-sdk?
1313

docs/SELF_REVIEW.md

Lines changed: 19 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -4,10 +4,28 @@ Automated reviewers (Codex, CodeRabbit, CI linters) catch bugs not because
44
they're smarter than humans or agents, but because they apply adversarial
55
checks consistently on every diff. Self-review tends to verify happy paths
66
and ship. This doc is the set of adversarial checks to run against your own
7-
code *before* declaring a change ready.
7+
code *before opening the PR* — so that what bots would flag in rounds 2–5
8+
is caught in commit 1 instead.
89

910
## When to apply
1011

12+
### Timing
13+
14+
**Run self-review BEFORE opening the PR, not after bots converge.**
15+
16+
Self-review is cheap (~5 minutes per PR). Bot review is expensive
17+
(~1–2 hours per round, often 3–5 rounds before convergence). Running
18+
self-review first catches what the bots would eventually flag, in the
19+
original commit — eliminating most of the review-loop cost.
20+
21+
If the temptation is "let bots find issues so I don't have to," re-read
22+
the [Review-Loop Discipline](UPSTREAM_SYNC.md#review-loop-discipline)
23+
section. The economics never favor that approach: one round of bot wait +
24+
triage + fix + push costs more than the self-review that would have
25+
pre-empted it.
26+
27+
### Triggers
28+
1129
Any change that introduces novel logic — especially:
1230

1331
- New regex, substitution, or tokenization pass

0 commit comments

Comments
 (0)