Skip to content

feat(chat): add Transcripts API + rename per-thread cache to threadHistory#448

Merged
dancer merged 7 commits into
mainfrom
feat/transcripts-api
May 5, 2026
Merged

feat(chat): add Transcripts API + rename per-thread cache to threadHistory#448
dancer merged 7 commits into
mainfrom
feat/transcripts-api

Conversation

@bensabic
Copy link
Copy Markdown
Contributor

@bensabic bensabic commented May 5, 2026

Adds a new bot.transcripts API for cross-platform per-user message persistence,
and renames the existing per-thread cache from messageHistory to threadHistory
so the two persistence concepts don't share a "messages" name.

What's new

Transcripts API (bot.transcripts, gated on ChatConfig.transcripts + ChatConfig.identity):

  • append, list, count, delete keyed by a stable cross-platform user key
    resolved by ChatConfig.identity once per inbound message during dispatch
    (cached on the Message instance as message.userKey).
  • Backed by the existing StateAdapter.appendToList primitive, so every built-in
    state adapter (memory, redis, ioredis, pg) supports it without changes.
  • delete is implemented via a tombstone written with appendToList(key, _, { maxLength: 1 })
    because state.delete only addresses the k/v namespace; list()/count()
    filter the tombstone out. This avoids extending the StateAdapter contract.
  • Single-entry / time-range deletes are intentionally not part of this API —
    the underlying appendToList primitive can't support them safely under
    concurrent writes.

messageHistorythreadHistory rename (with full backwards compatibility):

Old name New name
ChatConfig.messageHistory ChatConfig.threadHistory
Adapter.persistMessageHistory Adapter.persistThreadHistory
MessageHistoryCache ThreadHistoryCache
MessageHistoryConfig ThreadHistoryConfig
message-history.ts thread-history.ts
  • Both ChatConfig.threadHistory and ChatConfig.messageHistory are read at
    construction; threadHistory takes precedence when both are set.
  • Both Adapter.persistThreadHistory and Adapter.persistMessageHistory are
    read at the persistence sites; either flag enables persistence.
  • MessageHistoryCache / MessageHistoryConfig re-exported as @deprecated
    aliases of the new names.
  • The state-adapter storage key prefix (msg-history:) is intentionally
    unchanged
    — renaming it would silently orphan existing user data.
  • @chat-adapter/telegram and @chat-adapter/whatsapp updated to use
    persistThreadHistory. Custom adapters built against persistMessageHistory
    continue to work.

Public types added/exported

TranscriptsApi, TranscriptEntry, TranscriptRole, TranscriptsConfig,
AppendInput, AppendOptions, ListQuery, CountQuery, DeleteTarget,
IdentityResolver, IdentityContext, DurationString, ThreadHistoryCache,
ThreadHistoryConfig.

ChatInstance interface gains a readonly transcripts: TranscriptsApi so
callers typed against the public interface can reach bot.transcripts.

Docs

  • New guide: Conversation history
    covering both threadHistory and the Transcripts API together with usage patterns.
  • New API reference: Transcripts.
  • Both wired into the docs sidebar / API reference meta.json.

Example

examples/nextjs-chat — AI mode handler (onSubscribedMessage) now persists
inbound user messages and assistant replies via bot.transcripts, and reads
context from bot.transcripts.list({ threadId }) when the adapter doesn't
expose fetchMessages. New transcripts and clear-transcripts action
buttons demonstrate list / count / delete. TEST_USER_KEY is hardcoded
and clearly labelled for demo purposes.

Test plan

  • pnpm validate (knip + check + typecheck + 914 tests + build) — all 16 tasks pass
  • pnpm --filter chat test — 914 tests, 21 files, all green
  • pnpm --filter docs build — 113 static pages generated, no broken links

Migration notes

For consumers on the previous messageHistory API, no code changes
required
— the deprecated names continue to work and read the same data.
Tests, types, and JSDoc all carry @deprecated hints toward the new names.

Checklist

  • All commits are signed and verified
  • pnpm validate passes
  • Changeset added (.changeset/transcripts-api.md minor for chat;
    .changeset/thread-history-rename.md minor for chat, patch for
    @chat-adapter/telegram + @chat-adapter/whatsapp)
  • Documentation updated (new guide + API reference + sidebar entries)

bensabic added 7 commits May 5, 2026 17:00
…History

Introduce `bot.transcripts` for cross-platform per-user message persistence.
When `ChatConfig.transcripts` and `ChatConfig.identity` are configured, every
inbound message has its `userKey` resolved during dispatch and the API exposes
`append` / `list` / `count` / `delete` keyed by that user. Backed by the
existing `StateAdapter.appendToList` primitive — every built-in state adapter
supports it with no contract changes.

Rename the existing per-thread history cache from `messageHistory` to
`threadHistory` (with backwards compat for `ChatConfig.messageHistory` and
`Adapter.persistMessageHistory`) so the two persistence layers don't share a
"messages" name. The state-adapter storage key prefix is unchanged so existing
data isn't orphaned.

`delete()` writes a tombstone via `appendToList(key, _, { maxLength: 1 })`
rather than `state.delete(key)`, because `state.delete` only addresses the
k/v namespace on every non-memory state adapter — `list()` and `count()`
filter the tombstone out so the API contract is preserved.
Add a Features-style "Conversation history" guide (`/docs/conversation-history`)
walking through identity resolution, the LLM-context append/list pattern,
filtering, and per-user deletion for DSR flows.

Add an API reference page at `/docs/api/transcripts` with `<TypeTable>` blocks
for `ChatConfig.transcripts`, `ChatConfig.identity`, every method on
`bot.transcripts`, and the `TranscriptEntry` shape.

Wire both into the corresponding `meta.json` files.
Replace the brittle `threadState.history` shim with `bot.transcripts.list({ ..., threadId, limit })` as the fallback context source for platforms without
`fetchMessages` (Telegram, WhatsApp). Drop the `history` field from
`ThreadState` accordingly.

Add a hardcoded `TEST_USER_KEY = "test-user"` so the API can be exercised
without juggling real user identities, plus "Show Transcripts" and
"Clear Transcripts" buttons in the welcome card so the store can be
inspected and reset from chat.
Polish on top of the Transcripts API + threadHistory rename, addressing
review concerns before merge.

Public surface (`types.ts`, `index.ts`):
- Expose `transcripts` on the `ChatInstance` interface so callers typed
  against the public interface can reach `bot.transcripts`.
- Promote the `count` argument to a named `CountQuery` interface,
  matching `DeleteTarget` / `ListQuery`. Exported from `index.ts`.
- Document on `TranscriptsApi.list()` that pagination is intentionally
  out-of-scope — the store keeps at most `maxPerUser` entries per user.
- Reconcile the `TranscriptEntry.id` JSDoc with the implementation:
  UUID assigned at append time, returned in append order, not
  lexicographically sortable; use `timestamp` for cross-store ordering.

Wiring (`chat.ts`):
- Include `threadId` in the identity-resolver failure log context so
  operators can correlate failures with the source thread.

Stale-reference sweep:
- Replace lingering "Messages API" / `chat.messages` /
  `messages.storeFormatted` strings in shipped JSDoc with the new
  `transcripts` names (these ride into `.d.ts` and are user-visible).
- Fix the dead `[Messages API](./messages.ts)` link in the existing
  thread-history-rename changeset.
Fill gaps in the Transcripts API + threadHistory rename test suite:

`chat.test.ts` (persistThreadHistory block):
- top-level `config.messageHistory` (deprecated alias) flows through
  to the per-thread cache when `threadHistory` is unset
- `threadHistory` takes precedence over `messageHistory` when both are
  set — pinned by asserting `appendToList` receives the new config's
  `maxLength` / `ttlMs`
- both `persistThreadHistory` and `persistMessageHistory` set on the
  adapter still triggers persistence

`transcripts-wiring.test.ts`:
- sync resolver returning a plain string populates `message.userKey`
- resolver returning `""` is treated as no userKey (truthy check at
  the dispatch hook would silently flip if a future change moved to
  `!== undefined`)

`transcripts.test.ts`:
- concurrent append/delete/append interleave preserves invariants:
  `count()` and `list()` agree (no tombstone leak), no pre-delete
  entry survives, and the post-delete result is bounded by the two
  concurrent appends
- `formatted` rows in the AppendInput / TranscriptEntry TypeTables
  now render `FormattedContent | undefined` (the alias actually
  exported from `chat`), instead of `Root | undefined` which would
  force readers to pull the type from `mdast` directly.
- `count` signature uses the new `CountQuery` named type, with a
  one-liner describing its single field.
The action handler was relabelled to `transcripts` when it was wired
to `bot.transcripts.list`, but the leading comment still read
"Demonstrate fetchMessages and allMessages" from the previous
iteration. Update it to describe the transcripts demo.
@bensabic bensabic requested a review from a team as a code owner May 5, 2026 12:46
@vercel
Copy link
Copy Markdown
Contributor

vercel Bot commented May 5, 2026

The latest updates on your projects. Learn more about Vercel for GitHub.

Project Deployment Actions Updated (UTC)
chat Ready Ready Preview, Comment, Open in v0 May 5, 2026 0:47am
chat-sdk-nextjs-chat Ready Ready Preview, Comment, Open in v0 May 5, 2026 0:47am

@dancer dancer merged commit 46d183b into main May 5, 2026
13 checks passed
@dancer dancer deleted the feat/transcripts-api branch May 5, 2026 22:25
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants